现在有几个类似 ORM 的库正在开发中。
在邮件列表中,一些(聪明的)人最近描述了一些其他模型来说明这可能是如何工作的。这些库中的每一个都对问题采取了相当不同的方法,因此请务必查看它们。
例如,这是一个使用 Oyako的扩展示例。这个库还没有准备好生产并且仍在大量开发中,所以这个例子可能会在一周内失效,但它已经到了那里。无论如何,这都是一个概念验证。给它一些时间,有人会想出一个好的图书馆。
请注意,这clojure.contrib.sql
已经允许您从数据库中获取记录(通过 JDBC)并最终得到表示记录的不可变哈希映射。因为数据最终出现在法线贴图中,所以在贴图上工作的无数 Clojure 核心函数都已经在这个数据上工作了。
ActiveRecord 还能为您提供什么?我能想到几件事。
简洁的 SQL 查询 DSL
我在心理上对此建模的方式:首先定义表之间的关系。这不需要突变或对象。这是一个静态的描述。AR 将这些信息分散在一堆类中,但我将其视为一个单独的(静态)实体。
使用定义的关系,您可以以非常简洁的方式编写查询。以亲子为例:
(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 风格的对象或突变或实现继承呢?首先获取记录(作为不可变映射),然后通过一系列函数将其线程化,assoc
根据需要将新值添加到其上,然后将其填充回数据库或通过调用其上的函数将其删除。
一个聪明的库可以利用元数据来跟踪哪些字段已被更改,以减少进行更新所需的查询量。或者标记记录,以便数据库函数知道将其粘贴回哪个表。我认为 Carte 甚至会进行级联更新(当父记录发生更改时更新子记录)。
验证,钩子
我认为其中大部分属于数据库而不是 ORM 库。例如,级联删除(删除父记录时删除子记录):AR 有办法做到这一点,但您可以将一个子句扔到数据库中的表上,然后让您的数据库处理它,再也不用担心了。与多种约束和验证相同。
但是如果你想要钩子,可以使用简单的旧函数或多方法以非常轻量级的方式实现它们。在过去的某个时候,我有一个数据库库,它在 CRUD 周期的不同时间调用钩子,例如after-save
或before-delete
. 它们是对表名进行调度的简单多方法。这使您可以根据需要将它们扩展到您自己的表中。
(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 复杂,但有时简单就足够了。
查看此库以获取定义挂钩的另一个示例。
迁移
点菜有这些。我没有过多考虑它们,但是对数据库进行版本控制并将数据插入其中似乎并没有超出 Clojure 的可能性范围。
抛光
AR 的许多优点来自于命名表和命名列的所有约定,以及用于大写单词和格式化日期等的所有便利功能。这与 OO 与非 OO 无关;AR 只是有很多润色,因为它已经投入了很多时间。也许 Clojure 还没有用于处理数据库数据的 AR 类库,但请给它一些时间。
所以...
与其拥有一个知道如何销毁自身、变异自身、保存自身、将自身与其他数据关联、获取自身等的对象,不如拥有只是数据的数据,然后定义处理该数据的函数:它,销毁它,在数据库中更新它,获取它,将它与其他数据相关联。这就是 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 方式编写代码的所有好处。