In 2008 I discovered Clojure, and I was extremely excited to finally have an easy entry-way to Lisp. Pretty early on I wanted to get my head around one of Lisp's killer features: macros. A little playing around in the REPL was easy but not satisfying: I wanted to build something powerful.
In retrospect, I went too far trying to build in Rails-like magic, so I want to point out the design flaws that I recommend you avoid in your own work and recommend that you choose something other than clj-record for your Clojure projects that use an RDBMS.
The API of clj-record is all in one macro:
It commits a few macro sins.
- It looks at the namespace from which you call it to find the name of your model.
- It intentionally captures a
dbvar from your namespace.
- It defs a bunch of fns in your namespace (most of which are just partially
applied versions of
So you do this:
and that turns into something like this (leaving out the
This provided really good practice (and an example I've gone back to more than once) for quoting, syntax quoting, unquoting, and unquote-splicing.
But this sucks!
What init-model should do is return a data-structure that you then pass to clj-record's find, insert, and update fns. In addition to being simpler to understand than the hidden registry of model metadata that clj-record has to maintain, it would make it more natural to write generic, composable code, since the model itself would be a value.
If that were all init-model needed to do, the code (both inside clj-record and user code) would probably be even simpler if there were no macro at all. A set of functions could enhance the model data-structure as needed so that even model setup code could be composed like any other series of fn-calls.
Maybe it would be worthwhile having a macro that went something like this:
This macro would do a
def, but it's completely intuitive what that def is.
The third "sin" on my list above was about defining a bunch of fns in your
namespace. I call this a sin because it's not clear from reading the code
that there are a bunch of vars defined, making both discovery and debugging
There may be cases where "hidden defs" are justified by performance gains or
code reduction, but the fns defined by init-model don't pass that test
(clj-record.core/find-records widget attributes) is no worse and
more easily composed with other generic model-processing fns than
If no one else were writing libraries, I'd try to remedy this in the next major revision of clj-record, but someone else has done this work (or something very much like it) already. Chris Granger wrote korma, and it offers similar functionality in a saner way. I'm also not using Clojure with a SQL database, so I wouldn't be able to eat my own dogfood.
If you're modeling entities in a relational database, I'd recommend
korma over clj-record. There may be even better options for you
(including just using
clojure.java.jdbc directly), so look around.
If you're writing macros, don't capture vars, don't make their functionality
depend on globals like
*ns*, and consider alternatives to creating vars
in the caller's namespace. While you're at it, consider whether the job would
be better done with plain old functions.