现在有几个类似ORM的库正在开发中。
在邮件列表上,一些(聪明的)人最近描述了一些其他模型如何工作。每个库都采用了不同的方法来解决问题,所以一定要查看它们所有的内容。
这里有一个扩展示例,例如使用Oyako。 这个库还没有准备好投入生产,并且仍在积极开发中,因此示例可能在一周内无效,但它正在逐步完善。 无论如何,这只是一个概念验证。 给它一些时间,某人将会推出一个好的库。
请注意,
clojure.contrib.sql
已经让您可以通过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)))
保存/更新/删除记录
为什么需要面向对象的风格、变异或实现继承呢?首先获取记录(作为不可变映射),然后将其通过一系列函数进行处理,根据需要assoc
新值,最后通过调用函数将其放回数据库或删除它。
聪明的库可以利用元数据来跟踪哪些字段已被更改,以减少执行更新所需的查询量。 或者标记记录,使DB函数知道将其放回哪个表中。 我认为Carte甚至可以进行级联更新(在更改父记录时更新子记录)。
验证、钩子
我认为这些大部分应该属于数据库而不是ORM库。例如,级联删除(删除父记录时删除子记录):AR有一种方法可以做到这一点,但您可以将一个子句添加到DB中的表中,然后让您的DB处理它,从而永远不必担心。许多类型的约束和验证也是如此。
但是,如果您需要钩子,可以使用普通的函数或多态轻松地实现它们。在过去的某个时刻,我有一个数据库库,在CRUD周期的不同时间调用钩子,例如after-save
或before-delete
。它们是在表名上分派的简单多方法。这使您可以根据需要扩展它们到自己的表中。
(defmulti before-delete (fn [x] (table-for x)))
(defmethod before-delete :default [& _])
(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))
简单易扩展,只需花费几秒钟即可编写。看不到任何面向对象的东西。也许没有AR那么复杂,但有时候简单就足够了。
查看此库,以获取另一个定义钩子的示例。
迁移
Carte有这些功能。我还没有仔细思考过它们,但是对于Clojure来说,将数据库版本化并将数据导入其中似乎并不超出可能性的范围。
优雅
AR的好处在于命名表和列的所有约定,以及大量便捷函数用于大写单词,格式化日期等等。这与面向对象或非面向对象无关; 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方式编写代码的所有好处。