hackerschool/community — pull request #44 by zachallaun (2014-06-30 15:44:39+00:00)
Title: Semi-experimental refactoring

This pull request introduces two primary changes: using [prismatic/om-tools](https://github.com/prismatic/om-tools) for less boilterplate-y component definition and a new `community.util.transform` utility for better model data transforms.

## prismatic/om-tools

The biggest thing this gives us is `defcomponent`, which is a bit of sugar on top of the usual `reify` business. However, this also affords us a bit more flexibility going forward due to its [mixin support](https://github.com/Prismatic/om-tools#mixin-tools). Mixins should allow us to better compose things like the autocomplete and resizing textarea behaviors.

I considered simply writing `defcomponent` myself but dismissed it as a waste of time for now because I anticipate wanting mixins very soon.

## community.util.transform

We do some transformations on data that arrives from our API. For instance, users have a `:name` constructed from their `:first-name` and `:last-name`, threads and subforums get a `:slug`, etc. These transformations are mutually recursive as well: a user might have notifications, which may contain a user they were mentioned by and the thread they were mentioned in. This nested data needs to be transformed as well.

`community.util.transform` lets us define data transformers. A transformer is a collection of transforms, and a transform specifies some function that should run on the value of a specific key. Examples:

```clj
(require '[community.util.transform :as t])

(t/transform {:x 1} {:x inc}) ;=> {:x 2}
(t/transform {:y {:x 1}} {:x inc}) ;=> {:y {:x 2}}
(t/transform [{:x 1}] {:x inc}) ;=> [{:x 2}]
```

`deftransformer` and `deftransform` work sort of like `defmulti` and `defmethod`, allowing us to incrementally build up a transformer and providing some sugar to reduce boilerplate.

```clj
(require '[community.util.transform :refer-macros [deftransformer deftransform])

(deftransformer api->model)

(deftransform api->model :user
{:as user :keys [first-name last-name]}
(assoc user :name (str first-name " " last-name)))

(api->model {:user {:first-name "Zach" :last-name "Allaun"}})
;;=> {:user {:first-name "Zach" :last-name "Allaun" :name "Zach Allaun"}}

;; except that :user is sometimes :mentioned-by, so we can do the following:
(deftransform api->model [:user :mentioned-by]
...)

;; except that sometimes we have a vector of users called :autocomplete-users, so we can do the following:
(deftransform api->model {:single [:user :mentioned-by] :many :autocomplete-users}
...)
```

Using this to define our transformations drastically reduces boilerplate and prevents us from forgetting to apply a nested transformation, since all applicable transformations are recursively applies automatically.