clj-record is a library inspired by Rails’ ActiveRecord but implemented in the
Clojure programming language and intended to use its
functional idioms. It’s a fairly thin layer on top of clojure.contrib.sql that
provides validation, associations, and callbacks.
Contributions are welcome, as are recommendations for how to improve the API.
To define a model (in this case called “employee”), you do something like this.
(ns com.example.employee
(:require clj-record.boot))
(clj-record.core/init-model)
It’ll expect there to be an employees table. (At the moment you can’t specify a different table name.)
The (clj-record.core/init-model) macro form with no extra arguments will expand
into function definitions for basic crud operations:
- get-record (by id)
- find-records (by map of attributes)
- insert (from a map of attributes, returning the generated id)
- create (from a map of attributes, returning the record itself)
- update (from a map of attributes including :id)
- destroy-record (from a map of attributes including :id)
See the functions of the same name in
clj-record.core
for documentation.
(The model-name argument of the functions in clj-record.core is not needed
when calling functions on the model namespace.)
Additional optional arguments can generate richer functionality.
Do this.
(ns ...)
(clj-record.core/init-model
(:associations
(belongs-to account)
(has-many subscriptions)))
Then you can do things like this.
(let [mikey (user/get-record 2)
subs (user/find-subscriptions mikey)]
(doseq [subscription subs] (println (format "%s subscribed to %s" (:name mikey) (:name sub))))
(user/destroy-subscriptions mikey)
(println "But not any more."))
Do this.
(ns ...)
(clj-record.core/init-model
(:validation
(:name "Longer please." #(> (count %) 3))))
Then you get validation errors like this.
=> (let [errors (user/validate {:name "POO"})]
(errors :name)
["Longer please."]
…have just started making their way into working. It’s about what you’d expect.
(clj-record.core/init-model
(:callbacks
(:before-save fn-that-transforms-a-record)))
Do this.
(ns ...)
(clj-record.core/init-model
(:serialization (:grades)))
Then you can persist Clojure data structures (and many other Java types) into char/varchar columns in your database.
Attribute serialization uses clojure.core’s pr and read functions, so anything they support, clj-record supports.
clj-record is being TDD’d using clojure.contrib.test-is, largely with high-level full-stack tests, so see
the tests
for details of everything that works.
See TODO.txt
for what else I’m thinking about, and feel free to suggest.
Run ./test as a clj script to run tests. In addition to clojure-contrib, you’ll need
Apache Derby on your classpath.
(Or you can uncomment and modify a different db-spec in clj-record/test/config.clj and use MySQL or PostgreSQL.)
…is awful. Recommendations are welcome.
Brian Doyle for early interest and ideas.
Stephen Gilardi for making helpful changes to clojure.contrib.sql.
Raja Ramachandran for initial implementation of PostgreSQL support.
tunde ashafa for initial implementation of MySQL support and the clj-record.query API.
Copyright 2009 John D. Hume and released under an MIT license.