在现在的作品中有一些ORM样的图书馆。
> clj-record
> Carte
> Oyako(免责声明,我写了这个。)
> ClojureQL是从我可以看到的更多的SQL生成lib,但值得一提。
在邮件列表中,一些(聪明)的人最近描述了一些other models for how this might work.这些图书馆中的每一个都对这个问题采取了相当不同的方法,所以一定要看看它们。
这是一个extended example使用Oyako,例如。这个图书馆没有生产准备,仍然处于很大的发展阶段,所以这个例子可能在一个星期内是无效的,但是它到了那里。无论如何,这是一个概念验证。给它一些时间,有人会想出一个好的图书馆。
请注意,clojure.contrib.sql已经允许您从DB(通过JDBC)获取记录,并且最终使用表示记录的不可变哈希映射。因为数据最终在正常的地图中,所有在地图上工作的无数Clojure核心功能都已经在这个数据上工作了。
ActiveRecord还能给你什么?我可以想到几件事情。
简洁的SQL查询DSL
我在精神上建模的方式:首先你定义表之间的关系。这不需要突变或对象。这是一个静态描述。 AR将这些信息分散在一堆类中,但我将其视为一个单独的(静态)实体。
使用定义的关系,您可以以非常简洁的方式编写查询。以Oyako为例:
(def my-data (make-datamap db [:foo [has-one :bar]]
[:bar [belongs-to :foo]]))
(with-datamap my-data (fetch-all :foo includes :bar))
然后你会有一些foo对象,每个都有一个:bar键,列出你的酒吧。
在Oyako,“数据图”只是一张地图。查询本身是一张地图。返回的数据是地图的向量(地图的向量)。所以你最终得到一个标准的,简单的方式来构建和操纵和迭代所有这些东西,这是很好的。添加一些糖(宏和正常功能),让您更简洁地创建和操作这些地图,最终会非常强大。这只是一个方法,有很多方法。
如果您像Sequel这样的图书馆,另一个例子,你有这样的事情:
Artist.order(:name).last
但是为什么这些函数必须是存在于对象中的方法? Oyako中的等价物可能是:
(last (-> (query :artist)
(order :name)))
保存/更新/删除记录
再次,为什么你需要OO样式的对象或突变或实现继承?首先获取记录(作为不可变的地图),然后通过一系列函数进行线程化,根据需要将新值重新添加到数据库中,然后通过调用其中的函数将其删除。
一个聪明的图书馆可以使用元数据来跟踪哪些字段已被更改,以减少进行更新所需的查询量。或者标记记录,以便DB功能知道哪个表可以重写。 Carte甚至进行级联更新(更新子记录时父记录被更改),我想。
验证,挂钩
我看到的大部分属于数据库而不是ORM库。例如,级联删除(删除父记录时删除子记录):AR有一种方法可以执行此操作,但您只需将一个子句放在DB中的表上,然后让DB处理它,再也不用担心。与许多种约束和验证相同。
但是,如果你想要钩子,它们可以使用简单的旧函数或多方法以非常轻量的方式实现。在过去的某个时刻,我有一个数据库库,在CRUD循环中的不同时间调用挂钩,例如,保存后或删除前。他们是简单的多方法调度表名。这样可以让您将它们扩展到您自己的表格。
(defmulti before-delete (fn [x] (table-for x)))
(defmethod before-delete :default [& _]) ;; do nothing
(defn delete [x] (when (before-delete x) (db-delete! x) (after-delete x)))
后来作为最终用户我可以写:
(defmethod before-delete ::my_table [x]
(if (= (:id x) 1)
(throw (Exception. "OH NO! ABORT!"))
x))
容易和可扩展,花了几秒钟写。没有OO在眼前。不如AR那么复杂,但有时候简单就够了。
查看this library另一个定义钩子的例子。
迁移
Carte有这些。我没有想过太多,但是对数据库进行版本化并将数据压缩到其中似乎不超出Clojure的可能性。
抛光
AR的很多优点来自命名表和命名列的所有约定,以及大写字和格式化日期等的所有便利函数。这与OO与非OO无关; AR只是有很多的波兰,因为很多时间已经进入了它。也许Clojure没有用于处理数据库数据的AR类库,但给它一些时间。
所以…
而不是拥有一个知道如何破坏自身的对象,它可以自己进行变异,保存自身,将自身与其他数据相关联,获取自身等等,而不是仅具有数据的数据,然后定义对该数据有效的功能:save它,销毁它,在数据库中更新它,获取它,将其与其他数据相关联。这就是Clojure对数据的一般操作,数据库中的数据也没有什么不同。
Foo.find(1).update_attributes(:bar => "quux").save!
=> (with-db (-> (fetch-one :foo :where {:id 1})
(assoc :bar "quux")
(save!)))
Foo.create!(:id => 1)
=> (with-db (save (in-table :foo {:id 1})))
这样的东西它是从对象工作的方式内向外,但它提供相同的功能。但是在Clojure中,您也可以以FP的方式获得编写代码的所有好处。