Forked from samroberton/gist:d72cedaf225526d9007a
Last active
August 29, 2015 14:24
-
-
Save sthomp/05ac813276f8c7a927bf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn unjoin | |
"Given a seq `rows` as retrieved from a database select involving a join, | |
return a vector of nested entities, where the top-level entities have keys | |
`parent-keys`, plus a key for each key of `child-map`, where the value for | |
that key is a vector of values specified by the value in `child-map`. | |
Where the value in `child-map` is a simple keyword, the resulting vector will | |
contain the corresponding values for that keyword in `rows`. | |
Eg (unjoin [{:id 1, :a :foo} {:id 1, :a :bar}] [:id] {:vals :a}) => | |
[{:id 1, :vals [:foo :bar]}] | |
Where the value in `child-map` is a vector of keywords, the resulting vector | |
will contain maps, each with the specified keys selected from `rows`. | |
Eg (unjoin [{:id 1, :a :foo, :b :bar} {:id 1, :a :baz, :b :quux}] [:id] | |
{:vals [:a :b]}) => | |
[{:id 1, :vals [{:a :foo, :b :bar}, {:a :baz, :b :quux}]}] | |
Where the value in `child-map` is a vector of keywords, but with a map as the | |
last entry in the vector, `unjoin` will be called recursively, with the keys | |
in the vector as `parent-keys`, and with the map as `child-map`, resulting in | |
a second level of nesting with the result." | |
[rows parent-keys child-map] | |
(mapv (fn [child-rows] | |
(into (select-keys (first child-rows) parent-keys) | |
(for [[k child-action] child-map] | |
(let [val (cond (and (vector? child-action) | |
(map? (last child-action))) | |
;; [:a :b {:c :foo}] => recursively unjoin | |
(filter #(some (fn [x] (if (sequential? x) | |
(not-empty x) | |
x)) | |
(vals %)) | |
(unjoin child-rows | |
(vec (butlast child-action)) | |
(last child-action))) | |
(vector? child-action) | |
;; vector of keys to select | |
(filter #(some identity (vals %)) | |
(map #(select-keys % child-action) child-rows)) | |
(ifn? child-action) | |
(filter identity | |
(map child-action child-rows)))] | |
[k (vec val)])))) | |
(partition-by #(select-keys % parent-keys) rows))) | |
(deftest test-unjoin | |
(is (= [{:id 1, :a :b, :c :d, :e-vec [:f :g :h]}] | |
(unjoin [{:id 1, :a :b, :c :d, :e :f} | |
{:id 1, :a :b, :c :d, :e :g} | |
{:id 1, :a :b, :c :d, :e :h}] | |
[:id :a :c] | |
{:e-vec :e}))) | |
(is (= [{:id 1, :a :b, :c :d, :e-vec [:f :g]} | |
{:id 2, :a :b, :c :d, :e-vec [:h]}] | |
(unjoin [{:id 1, :a :b, :c :d, :e :f} | |
{:id 1, :a :b, :c :d, :e :g} | |
{:id 2, :a :b, :c :d, :e :h}] | |
[:id :a :c] | |
{:e-vec :e}))) | |
(is (= [{:id 1, :a :b, :coll [{:c :foo, :d :bar} | |
{:c :baz, :d :quux}]}] | |
(unjoin [{:id 1, :a :b, :c :foo, :d :bar} | |
{:id 1, :a :b, :c :baz, :d :quux}] | |
[:id :a] | |
{:coll [:c :d]}))) | |
(is (= [{:id 1, :a :b, :coll-1 [{:c :d, :x 1, :coll-2 [:foo :bar :baz]} | |
{:c :e, :x 2, :coll-2 [:quux :blort]}]} | |
{:id 2, :a :b, :coll-1 []}] | |
(unjoin [{:id 1, :a :b, :c :d, :x 1, :val :foo} | |
{:id 1, :a :b, :c :d, :x 1, :val :bar} | |
{:id 1, :a :b, :c :d, :x 1, :val :baz} | |
{:id 1, :a :b, :c :e, :x 2, :val :quux} | |
{:id 1, :a :b, :c :e, :x 2, :val :blort} | |
{:id 2, :a :b, :c nil, :x nil, :val nil}] | |
[:id :a] | |
{:coll-1 [:c :x {:coll-2 :val}]})))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment