如何从clojure中的关系数据库中获取一个_model_数据?

San*_*eep 20 mysql postgresql functional-programming clojure compojure

我已经在twitter和#clojure IRC频道上提出了这个问题,但没有得到任何答复.

有几篇关于Clojure-for-Ruby程序员,Clojure-for-lisp程序员的文章......但缺少的部分是Clojure for ActiveRecord程序员.

有关于与MongoDB,Redis等进行交互的文章 - 但这些都是一天结束时的关键价值商店.但是,来自Rails背景,我们习惯于在继承方面考虑数据库 - has_many,polymorphic,belongs_to等.

关于Clojure/Compojure + MySQL(ffclassic)的几篇文章- 深入研究sql.当然,ORM可能导致阻抗不匹配,但事实仍然是,在像ActiveRecord一样思考之后,很难以其他方式思考.

我相信关系数据库非常适合面向对象的范例,因为它们本质上就是集合.像activerecord这样的东西非常适合建模这些数据.对于例如博客 - 简单地说

class Post < ActiveRecord::Base
  has_many :comments
 end


 class Comment < ActiveRecord::Base
   belongs_to :post
 end
Run Code Online (Sandbox Code Playgroud)

如何在Clojure中对此进行建模 - 这是如此严格的反OO?如果提到所有函数式编程语言,问题可能会更好,但我更感兴趣的是Clojure的观点(以及Clojure示例)

Bri*_*per 18

现在有几个类似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))
Run Code Online (Sandbox Code Playgroud)

然后你会有一些foo对象,每个对象都有一个:bar列出你的栏的键.

在Oyako,"数据地图"只是一张地图.查询本身就是一张地图.返回的数据是地图矢量(地图矢量).所以你最终会得到一个标准的,简单的方法来构造,操作和迭代所有这些东西,这很好.添加一些糖(宏和普通函数),让您更容易地简明地创建和操作这些地图,并最终变得非常强大.这只是一种方式,有很多方法.

如果你看一下像Sequel这样的图书馆的另一个例子,你有以下几样的东西:

Artist.order(:name).last
Run Code Online (Sandbox Code Playgroud)

但为什么这些函数必须是生活在对象内部的方法?Oyako中的等价物可能是:

(last (-> (query :artist) 
          (order :name)))
Run Code Online (Sandbox Code Playgroud)

保存/更新/删除记录

同样,为什么你需要OO风格的对象或变异或实现继承呢?首先获取记录(作为不可变映射),然后通过一堆函数assoc将其线程化,根据需要将新值放入其中,然后将其填充回数据库或通过调用其上的函数将其删除.

一个聪明的库可以利用元数据来跟踪哪些字段已被更改,以减少进行更新所需的查询量.或者标记记录,以便DB函数知道将哪个表重新粘贴到其中.Carte甚至会进行级联更新(当父记录被更改时更新子记录),我想.

验证,钩子

我认为大部分都属于数据库,而不是ORM库.例如,级联删除(删除父记录时删除子记录):AR有一种方法可以执行此操作,但您只需将一个子句抛出到数据库中的表中,然后让您的数据库处理它,再也不用担心了.与许多种约束和验证相同.

但是如果你想要钩子,可以使用普通的旧函数或多方法以非常轻量级的方式实现它们.在过去的某个时刻,我有一个数据库库,在CRUD周期的不同时间调用钩子,例如after-savebefore-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)))
Run Code Online (Sandbox Code Playgroud)

然后作为最终用户,我可以写:

(defmethod before-delete ::my_table [x] 
  (if (= (:id x) 1)
    (throw (Exception. "OH NO! ABORT!"))
    x))
Run Code Online (Sandbox Code Playgroud)

容易和可扩展,并花了几秒钟写.看不到OO.不像AR那样复杂,但有时简单就足够了.

查看此库以获取定义钩子的另一个示例.

迁移

Carte有这些.我对它们没有太多考虑,但对数据库进行版本控制并将数据插入其中并不会超出Clojure的可能范围.

抛光

AR的很多好处来自于命名表和命名列的所有约定,以及用于大写单词和格式化日期等的所有便利功能.这与OO与非OO无关; AR只是有很多优点,因为很多时间都进入了它.也许Clojure还没有用于处理数据库数据的AR类库,但给它一些时间.

所以...

而不必知道如何自我毁灭的对象,变异本身,自己救自己,涉及自身其它数据,取本身,等等,而不是你的数据只是数据,然后你定义对这些数据的工作职能:保存它,销毁它,在DB中更新它,取出它,将它与其他数据联系起来.这就是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})))
Run Code Online (Sandbox Code Playgroud)

这样的事情.它从对象的工作方式内到外,但它提供相同的功能.但是在Clojure中,您还可以获得以FP方式编写代码的所有好处.


小智 5

这个问题暂时没有得到解答,但Korma http://sqlkorma.com是另一个项目,它也有助于减少SQL和Clojure之间的不一致.它最近已经开始使用,应该可以使用更新的Clojure版本.