Clojure ORM-ish stuff

Suppose I have this:

user> (def foo [{:id 1 :foo 123} {:id 2 :foo 456}])
#'user/foo
user> (def bar [{:foo_id 1 :bar 111} {:foo_id 1 :bar 222}])
#'user/bar

What I want is to "join" foo and bar so that each item in foo ends up with a sub-list of bars based on matching key fields.

In real life, these lists-of-hash-maps are coming out of a database via clojure.contrib.sql, so this is something I actually want to do pretty often. This is also vaguely similar to what you get out of a Rails-like ORM, where you end up with an object that has lists of sub-objects anywhere you have a one-to-many relationship.

Here's how I end up doing this in Clojure:

(defn one-to-many
  ([xs name ys f]
      (for [x xs :let [ys (filter (partial f x) ys)]]
        (assoc x name ys))))

Now I can do this:

user> (pprint (one-to-many foo :bars bar #(= (:id %1) (:foo_id %2))))
({:bars ({:foo_id 1, :bar 111} {:foo_id 1, :bar 222}), :id 1, :foo 123}
 {:bars (), :id 2, :foo 456})

And if I define a helper function:

(defn key=
  ([xkey ykey]
     #(= (xkey %1) (ykey %2))))

Then I can write it more concisely:

user> (pprint (one-to-many foo :bars bar (key= :id :foo_id)))
;; same as above

And if I have another "table" of data like this:

user> (def baz [{:foo_id 1 :baz 555} {:foo_id 2 :baz 999}])
#'user/baz

Then I can join them all like this:

user> (pprint (-> foo
                  (one-to-many :bars bar (key= :id :foo_id))
                  (one-to-many :bazzes baz (key= :id :foo_id))))
({:bazzes ({:foo_id 1, :baz 555}),
  :bars ({:foo_id 1, :bar 111} {:foo_id 1, :bar 222}),
  :id 1,
  :foo 123}
 {:bazzes ({:foo_id 2, :baz 999}), :bars (), :id 2, :foo 456})

This is pretty concise. It may be possible to do it in an even more concise way, (if so, do share). If I was willing to adhere to some Rails-y naming convention for my table names and for the id fields in my tables, I could make this shorter by not having to specify the names of the id fields, but I don't want to go there. It's trivial to write similar functions for a one-to-one relationship, or to use a join-table to "join" two tables with a many-to-many relationship.

I am happily surprised sometimes by how simple it is to roll my own version of things that previously seemed like dark magic. I used Rails for a long time and it seemed like a crapload of code must have gone into making the ORM work. But four lines of code gets me 75% of what I ever needed Rails' ORM for.

This may be more thanks to me opening my eyes a bit than to Clojure being awesome, but either way, I'll take it.

November 03, 2009 @ 10:21 AM PST
Cateogory: Programming

3 Comments

Frank
Quoth Frank on October 04, 2011 @ 6:57 AM PDT

Hi Brian,

I know this post is old, but I'm curious about something. As a new user and fan of clojure, I keep wondering why there isn't good rails-like functionality in clojure yet. Mainly the concept of defining a "model" or table in clojure code, and from that model, rendering CRUD screens, or forms, and maybe some other db functionality. I use Django in my job, and I love this feature. Do you have any thoughts on the progress of something like this in clojure? I'm asking you because you seem to have the most writing and code related to this subject.

Thanks!

Brian
Quoth Brian on October 04, 2011 @ 7:41 AM PDT

Probably the main reason there's no such library is just because no one's written it yet. Clojure is still very young.

Java offers ORM libraries of its own, and many devs are probably happy with those. Hibernate isn't too much different than ActiveRecord, though (in my opinion) Hibernate is far more clunky to use, just because Java is like that.

Clojure's immutable data structures clash a bit with the traditional concept of an ORM, where objects represent database records and mutating objects mutates your database. An ORM in Clojure will have to look different than that.

Non-SQL libraries are also popular nowadays, and many people use those, so don't have much need for SQL wrappers.

But there are a few libraries already. My completely-out-of-date oyako is one. carte is another. I'm still slowly but surely working on oyako, but other things have sapped up my time to the point where I have been unable to keep it up to date with Clojure development.

I have faith that a good ORM-like library for Clojure will show up. Web development (ring, compojure, enlive) is alive and well in Clojure, and a good database library is an itch someone will definitely scratch sooner or later.

Frank
Quoth Frank on October 04, 2011 @ 7:54 AM PDT

Ok. Thanks for your response! I was just having a look at Conjure, and it doesn't look too bad, but that's without trying it.

Speak your Mind

You can use Markdown in your comment.
Email/URL are optional. Email is only used for Gravatar.

Preview