A relational mapper in Clojure. If you're using relational database in Clojure then this library is for you.
Make calls like:
(find-all db-state :posts #{:authors :attachments} [:= post.id 1])
and get results like:
{:posts
{:title "Christmas"
:body "Merry Christmas!"
:id 1
:authors_id 10
:authors {:name "Rudolf" :id 10}
:attachments [{:name "rudolf.png" :id 100 :posts_id 1}
{:name "santa.png" :id 101 :posts_id 1}]
to achieve that though you have to first tell 'relational-mapper' what's the structure of your data and how to connect to the database, so the full, working example would look like this:
(ns your-project
(:require [relational-mapper :refer :all]
[relational-mapper.data-model :as data-model]))
(def associations {:authors {:posts {:type :has-many}
:attachments {:type :has-many :through :posts}}
:posts {:authors {:type :belongs-to}
:attachments {:type :has-many}}
:attachments {:authors {:type :belongs-to :through :posts}
:posts {:type :belongs-to}}})
(def db-config {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname (str "//" "localhost" ":" 5432 "/" "testdb")
:user "postgres-user"
:password "postgres-password"})
(def initial-db-state {:config db-config
:data-model {})
(def db-state (data-model/set-associations initial-db-state associations {})
(find-all db-state :posts #{:authors :attachments} [:= post.id 1])
relational-mapper
uses the same relations naming as 'Ruby On Rails`' 'ActiveRecord', which means:
-
posts
has-many
attachments
means thatattachments
hasposts_id
column that refers toid
column ofposts
table and there might be more than one attachment for one user (hence in response offind-all
function,attachments
is an array of hashes) -
attachments
has-one
file
means thatfile
hasattachments_id
column that refers toid
column inattachments
table. There can be only one file per attachment though (sofind-all
will returnfiles
as a hash and not an array) -
attachments
belongs-to
posts
means thatattachments
hasposts_id
column that refers toid
column inposts
table (it doesn't say anything about how many attachments per post is allowed) -
through
relation is used in case of indirect relations, so for example ifusers
has-many
posts
, andposts
has-many
attachments
, we can make a call tofind-all
that will give ususers
with all theattachments
of theirposts
.
Have in mind that unlike ActiveRecord, here associations are always plural (:posts {:authors :belongs-to}
and not :posts {:author :belongs-to
). The same applies to key names (users_id
, not user_id
). This is by design, and it's not likely to change.
Also, unlike 'ActiveRecord', here you can define through
association referring to belongs-to
association (the lack of this feature in 'ActiveRecord' is described for example here: https://www.ruby-forum.com/topic/74219)
Sometimes you need to set an association that is named differently than the target table name, for example posts
may have association authors
which refers to table users
(or another case: you need associations: created_by
and updated_by
, both referring to the same users
table). In such case you can use inverse-of
and model
in associations hash, e.g.:
(def associations {:users {:posts {:type :has-many :inverse-of :authors}}
:posts {:authors {:type :belongs-to :model :users}}})
By default keys of tables are assumed to be called id
and foreign keys are assumed to match the format association_id
(so, for example foreign key for table users
is called users_id
). If you want to change that, you can define key patterns in last attribute of the function set-associations
, e.g.
(def db-state (data-model/set-associations initial-db-state associations {
:foreign-key-format #(str % "_key")}))
With above settings relational_mapper
will expect foreign keys to match the pattern assocation_key
.
relational-mapper
uses Honey SQL for defining SQL conditions.
-
test it with MySQL (I used it only with PostgreSQL, it should work with MySQL too, but I haven't checked that),
-
improve performance (right now a database call is made per each requested table, while in some cases only one call could be made).
-
consider posibility of result having a deeper structure (result might be array of :posts with :authors and :attachments but then each attachment can have data from :files included)
Copyright © 2016 Krzysztof Herod
Distributed under the Eclipse Public License, the same as Clojure.