I have asked this question on twitter as well the #clojure IRC channel, yet got no responses.
我在twitter和#clojure IRC频道也问了这个问题,但没有得到任何回应。
There have been several articles about Clojure-for-Ruby-programmers, Clojure-for-lisp-programmers.. but what is the missing part is Clojure for ActiveRecord programmers .
有好几篇文章是关于clojure -for- ruby程序员,clojure -for-lisp程序员的。但是缺少的是对ActiveRecord程序员的Clojure。
There have been articles about interacting with MongoDB, Redis, etc. - but these are key value stores at the end of the day. However, coming from a Rails background, we are used to thinking about databases in terms of inheritance - has_many, polymorphic, belongs_to, etc.
已经有一些关于与MongoDB、Redis等进行交互的文章——但是这些都是重要的价值存储。但是,从Rails的背景出发,我们习惯于从继承的角度考虑数据库——has_many、polymorphic、belongs_to等等。
The few articles about Clojure/Compojure + MySQL (ffclassic) - delve right into sql. Of course, it might be that an ORM induces impedence mismatch, but the fact remains that after thinking like ActiveRecord, it is very difficult to think any other way.
关于Clojure/Compojure + MySQL的几篇文章(ffclassic)——钻研sql。当然,可能是ORM导致了阻抗不匹配,但事实是,在像ActiveRecord那样思考之后,很难再以其他方式思考。
I believe that relational DBs, lend themselves very well to the object-oriented paradigm because of them being , essentially, Sets. Stuff like activerecord is very well suited for modelling this data. For e.g. a blog - simply put
我相信关系型DBs很适合面向对象的范例,因为它们本质上是集合。activerecord之类的东西非常适合建模这些数据。例如,一个博客——简单地说
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
How does one model this in Clojure - which is so strictly anti-OO ? Perhaps the question would have been better if it referred to all functional programming languages, but I am more interested from a Clojure standpoint (and Clojure examples)
如何在Clojure中对其进行建模?如果问题涉及到所有函数式编程语言,那么这个问题可能会更好,但我更感兴趣的是Clojure(以及Clojure示例)
3 个解决方案
#1
18
There are a couple ORM-like libraries in the works nowadays.
现在有一些类似图书馆的图书馆正在建设中。
- clj-record
- clj-record
- Carte
- 菜单
- Oyako (Disclaimer, I wrote this one.)
- 小皇子(免责声明,这是我写的。)
- ClojureQL is more of an SQL-generating lib from what I can see, but it deserves mention.
- ClojureQL更像是一个生成sql的库,但我认为它值得提及。
On the mailing list, some (smart) people recently described some other models for how this might work. Each of these libraries takes a fairly different approach to the problem, so be sure to take a look at them all.
在邮件列表中,一些(聪明的)人最近描述了一些其他的模型来说明这是如何工作的。对于这个问题,这些库中的每一个都采用了完全不同的方法,所以一定要查看它们。
Here's an extended example using Oyako, for example. This library isn't production ready and still under heavy development, so the example may be invalid in a week, but it's getting there. It's a proof-of-concept in any case. Give it some time and someone will come up with a good library.
例如,这里有一个使用Oyako的扩展示例。这个库还没有准备好,还在大量开发中,因此这个示例可能在一周后就失效了,但它正在实现。无论如何,这都是一种概念验证。给它一些时间,有人会想出一个好的图书馆。
Note that clojure.contrib.sql
already lets you fetch records from a DB (via JDBC) and end up with immutable hash-maps representing records. Because the data ends up in normal maps, all of the myriad Clojure core functions that work on maps already work on this data.
注意,clojure.contrib。sql已经允许您从DB(通过JDBC)获取记录,并以表示记录的不可变的hashmap结束。因为这些数据最终会出现在法线地图中,所以所有在地图上工作的Clojure核心函数都已经在这个数据上工作了。
What else does ActiveRecord give you? I can think of a couple of things.
ActiveRecord还提供了什么?我能想到一些事情。
A concise SQL-query DSL
The way I model this mentally: First you define the relationship between the tables. This doesn't require mutation or objects. It's a static description. AR spreads this information out in a bunch of classes, but I see it as a separate (static) entity.
我在头脑中建模的方式是:首先定义表之间的关系。这并不需要突变或对象。这是一个静态的描述。AR在一堆类中传播这些信息,但我把它看作是一个单独的(静态的)实体。
Using the defined relationships, you can then write queries in a very concise manner. With Oyako for example:
使用已定义的关系,您可以以非常简洁的方式编写查询。与Oyako例如:
(def my-data (make-datamap db [:foo [has-one :bar]]
[:bar [belongs-to :foo]]))
(with-datamap my-data (fetch-all :foo includes :bar))
Then you'll have some foo
objects, each with a :bar
key that lists your bars.
然后你会有一些foo对象,每个都有一个:bar键,它列出了你的栏。
In Oyako, the "data map" is just a map. The query itself is a map. The returned data is a vector of maps (of vectors of maps). So you end up with a standard, easy way to construct and manipulate and iterate over all of these things, which is nice. Add some sugar (macros and normal functions), to let you concisely create and manipulate these maps more easily, and it ends up being quite powerful. This just just one way, there are a lot of approaches.
在绫子,“数据地图”只是一张地图。查询本身就是一个映射。返回的数据是一个映射向量(映射的矢量)。最后你得到了一个标准的,简单的方法来构造,操作和迭代所有这些东西,这很好。添加一些糖(宏和普通函数),让您更容易地创建和操作这些映射,最终它会非常强大。这只是一种方法,有很多方法。
If you look at a library like Sequel for another example, you have things like:
如果你再看一个像Sequel这样的库,你会有这样的东西:
Artist.order(:name).last
But why do these functions have to be methods that live inside of objects? An equivalent in Oyako might be:
但是为什么这些函数必须是存在于对象内部的方法呢?在Oyako的一个对等物可能是:
(last (-> (query :artist)
(order :name)))
Save/update/delete records
Again, why do you need OO-style objects or mutation or implementation inheritance for this? First fetch the record (as an immutable map), then thread it through a bunch of functions, assoc
ing new values onto it as needed, then stuff it back into the database or delete it by calling a function on it.
同样,为什么需要oo样式的对象、突变或实现继承?首先获取记录(作为不可变的映射),然后通过一系列函数对其进行线程化,并根据需要将新值添加到记录上,然后将其填充到数据库中,或者通过调用数据库上的一个函数将其删除。
A clever library could make use of metadata to keep track of which fields have been altered, to reduce the amount of querying needed to do updates. Or to flag the record so the DB functions know which table to stick it back into. Carte even does cascading updates (updating sub-records when a parent record is altered), I think.
一个聪明的库可以使用元数据来跟踪哪些字段被修改,以减少执行更新所需的查询量。或者标记记录,使DB函数知道要将其插入到哪个表中。Carte甚至做级联更新(当父记录被修改时更新子记录),我认为。
Validations, hooks
Much of this I see as belonging in the database rather than in the ORM libary. For example, cascading deletes (deleting child records when parent records are deleted): AR has a way to do this, but you can just throw a clause onto the table in the DB and then let your DB handle it, and never worry again. Same with many kinds of constraints and validations.
我将其中的大部分视为属于数据库,而不是ORM libary。例如,级联删除(删除父记录时删除子记录):AR有一种方法可以做到这一点,但是您可以将一个子句放到DB中的表中,然后让您的DB处理它,并且不再担心。许多约束和验证也是如此。
But if you want hooks, they can be implemented in a very lightweight way using plain old functions or multimethods. At some point in the past I had a database library that called hooks at different times in the CRUD cycle, for example after-save
or before-delete
. They were simple multimethods dispatching on table names. This lets you extend them to your own tables as you like.
但是如果您想要挂钩,它们可以用非常轻量级的方式实现,使用简单的旧函数或多方法。在过去的某个时候,我有一个数据库库,它在CRUD循环的不同时间调用hook,例如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)))
Then later as an end user I could write:
然后,作为最终用户,我可以这样写:
(defmethod before-delete ::my_table [x]
(if (= (:id x) 1)
(throw (Exception. "OH NO! ABORT!"))
x))
Easy and extensible, and took a couple seconds to write. No OO in sight. Not as sophisticated as AR maybe, but sometimes simple is good enough.
易于扩展,并花了几秒钟写。没有面向对象。也许没有AR那么复杂,但有时候简单就足够了。
Look at this library for another example of defining hooks.
看看这个库,看看另一个定义钩子的例子。
Migrations
Carte has these. I haven't thought much about them, but versioning a database and slurping data into it doesn't seem beyond the realm of possibility for Clojure.
菜单有这些。我并没有过多地考虑它们,但是对数据库进行版本控制并将数据吸收到其中似乎并不超出Clojure的可能性范围。
Polish
A lot of the good of AR comes from all the conventions for naming tables and naming columns, and all the convenience functions for capitalizing words and formatting dates and such. This has nothing to do with OO vs. non-OO; AR just has a lot of polish because a lot of time has gone into it. Maybe Clojure doesn't have an AR-class library for working with DB data yet, but give it some time.
AR的许多好处来自于命名表和命名列的所有约定,以及大写字母和格式化日期等的所有便利功能。这与OO和非OO无关;AR只是有很多的修饰,因为有很多时间。也许Clojure还没有用于处理DB数据的AR-class库,但是给它一些时间吧。
So...
Instead of having an object that knows how to destroy itself, mutate itself, save itself, relate itself to other data, fetch itself, etc., instead you have data that's just data, and then you define functions that work on that data: saves it, destroys it, updates it in the DB, fetches it, relates it to other data. This is how Clojure operates on data in general, and data from a database is no different.
而不是一个对象,知道如何摧毁自己,改变自己,拯救自己,与其他数据,获取本身,等等,而不是你有数据就是数据,然后在数据定义功能工作:保存它,摧毁它,更新数据库,获取它,与它的其他数据。这就是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})))
Something like that. It's inside-out from the way objects work, but it provides the same functionality. But in Clojure you also get all the benefits of writing code in an FP kind of way.
就像这样。它与对象的工作方式相反,但它提供了相同的功能。但是在Clojure中,您也可以从FP的方式中获得编写代码的所有好处。
#2
5
This question hasn't been answered in a while, but Korma http://sqlkorma.com is another project that also helps reduce the dissonance between SQL and Clojure. It's been worked on more recently and should work with more recent Clojure versions.
这个问题已经有一段时间没有答案了,但是Korma http://sqlkorma.com是另一个帮助减少SQL和Clojure之间不和谐的项目。它最近才被开发出来,应该可以用于更近期的Clojure版本。
#3
0
A new micro SQL DSL built upon clojure.contrib.sql has been started. It's on Github weighing in at a lightweight 76 loc.
一种新的基于clojure. managed b的微SQL DSL。sql已经启动。它在Github上,重量很轻,只有76 loc。
There's a lot of examples and theory behind the DSL on the author's blog, linked on his Github profile (new user SO hyperlink limit bah). Its design allows SQL to be abstracted into much more idiomatic Clojure, I feel.
作者博客上的DSL背后有很多例子和理论,链接到Github的个人资料(新用户SO超级链接限制bah)。我觉得,它的设计让SQL被抽象成更符合习惯的Clojure。
#1
18
There are a couple ORM-like libraries in the works nowadays.
现在有一些类似图书馆的图书馆正在建设中。
- clj-record
- clj-record
- Carte
- 菜单
- Oyako (Disclaimer, I wrote this one.)
- 小皇子(免责声明,这是我写的。)
- ClojureQL is more of an SQL-generating lib from what I can see, but it deserves mention.
- ClojureQL更像是一个生成sql的库,但我认为它值得提及。
On the mailing list, some (smart) people recently described some other models for how this might work. Each of these libraries takes a fairly different approach to the problem, so be sure to take a look at them all.
在邮件列表中,一些(聪明的)人最近描述了一些其他的模型来说明这是如何工作的。对于这个问题,这些库中的每一个都采用了完全不同的方法,所以一定要查看它们。
Here's an extended example using Oyako, for example. This library isn't production ready and still under heavy development, so the example may be invalid in a week, but it's getting there. It's a proof-of-concept in any case. Give it some time and someone will come up with a good library.
例如,这里有一个使用Oyako的扩展示例。这个库还没有准备好,还在大量开发中,因此这个示例可能在一周后就失效了,但它正在实现。无论如何,这都是一种概念验证。给它一些时间,有人会想出一个好的图书馆。
Note that clojure.contrib.sql
already lets you fetch records from a DB (via JDBC) and end up with immutable hash-maps representing records. Because the data ends up in normal maps, all of the myriad Clojure core functions that work on maps already work on this data.
注意,clojure.contrib。sql已经允许您从DB(通过JDBC)获取记录,并以表示记录的不可变的hashmap结束。因为这些数据最终会出现在法线地图中,所以所有在地图上工作的Clojure核心函数都已经在这个数据上工作了。
What else does ActiveRecord give you? I can think of a couple of things.
ActiveRecord还提供了什么?我能想到一些事情。
A concise SQL-query DSL
The way I model this mentally: First you define the relationship between the tables. This doesn't require mutation or objects. It's a static description. AR spreads this information out in a bunch of classes, but I see it as a separate (static) entity.
我在头脑中建模的方式是:首先定义表之间的关系。这并不需要突变或对象。这是一个静态的描述。AR在一堆类中传播这些信息,但我把它看作是一个单独的(静态的)实体。
Using the defined relationships, you can then write queries in a very concise manner. With Oyako for example:
使用已定义的关系,您可以以非常简洁的方式编写查询。与Oyako例如:
(def my-data (make-datamap db [:foo [has-one :bar]]
[:bar [belongs-to :foo]]))
(with-datamap my-data (fetch-all :foo includes :bar))
Then you'll have some foo
objects, each with a :bar
key that lists your bars.
然后你会有一些foo对象,每个都有一个:bar键,它列出了你的栏。
In Oyako, the "data map" is just a map. The query itself is a map. The returned data is a vector of maps (of vectors of maps). So you end up with a standard, easy way to construct and manipulate and iterate over all of these things, which is nice. Add some sugar (macros and normal functions), to let you concisely create and manipulate these maps more easily, and it ends up being quite powerful. This just just one way, there are a lot of approaches.
在绫子,“数据地图”只是一张地图。查询本身就是一个映射。返回的数据是一个映射向量(映射的矢量)。最后你得到了一个标准的,简单的方法来构造,操作和迭代所有这些东西,这很好。添加一些糖(宏和普通函数),让您更容易地创建和操作这些映射,最终它会非常强大。这只是一种方法,有很多方法。
If you look at a library like Sequel for another example, you have things like:
如果你再看一个像Sequel这样的库,你会有这样的东西:
Artist.order(:name).last
But why do these functions have to be methods that live inside of objects? An equivalent in Oyako might be:
但是为什么这些函数必须是存在于对象内部的方法呢?在Oyako的一个对等物可能是:
(last (-> (query :artist)
(order :name)))
Save/update/delete records
Again, why do you need OO-style objects or mutation or implementation inheritance for this? First fetch the record (as an immutable map), then thread it through a bunch of functions, assoc
ing new values onto it as needed, then stuff it back into the database or delete it by calling a function on it.
同样,为什么需要oo样式的对象、突变或实现继承?首先获取记录(作为不可变的映射),然后通过一系列函数对其进行线程化,并根据需要将新值添加到记录上,然后将其填充到数据库中,或者通过调用数据库上的一个函数将其删除。
A clever library could make use of metadata to keep track of which fields have been altered, to reduce the amount of querying needed to do updates. Or to flag the record so the DB functions know which table to stick it back into. Carte even does cascading updates (updating sub-records when a parent record is altered), I think.
一个聪明的库可以使用元数据来跟踪哪些字段被修改,以减少执行更新所需的查询量。或者标记记录,使DB函数知道要将其插入到哪个表中。Carte甚至做级联更新(当父记录被修改时更新子记录),我认为。
Validations, hooks
Much of this I see as belonging in the database rather than in the ORM libary. For example, cascading deletes (deleting child records when parent records are deleted): AR has a way to do this, but you can just throw a clause onto the table in the DB and then let your DB handle it, and never worry again. Same with many kinds of constraints and validations.
我将其中的大部分视为属于数据库,而不是ORM libary。例如,级联删除(删除父记录时删除子记录):AR有一种方法可以做到这一点,但是您可以将一个子句放到DB中的表中,然后让您的DB处理它,并且不再担心。许多约束和验证也是如此。
But if you want hooks, they can be implemented in a very lightweight way using plain old functions or multimethods. At some point in the past I had a database library that called hooks at different times in the CRUD cycle, for example after-save
or before-delete
. They were simple multimethods dispatching on table names. This lets you extend them to your own tables as you like.
但是如果您想要挂钩,它们可以用非常轻量级的方式实现,使用简单的旧函数或多方法。在过去的某个时候,我有一个数据库库,它在CRUD循环的不同时间调用hook,例如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)))
Then later as an end user I could write:
然后,作为最终用户,我可以这样写:
(defmethod before-delete ::my_table [x]
(if (= (:id x) 1)
(throw (Exception. "OH NO! ABORT!"))
x))
Easy and extensible, and took a couple seconds to write. No OO in sight. Not as sophisticated as AR maybe, but sometimes simple is good enough.
易于扩展,并花了几秒钟写。没有面向对象。也许没有AR那么复杂,但有时候简单就足够了。
Look at this library for another example of defining hooks.
看看这个库,看看另一个定义钩子的例子。
Migrations
Carte has these. I haven't thought much about them, but versioning a database and slurping data into it doesn't seem beyond the realm of possibility for Clojure.
菜单有这些。我并没有过多地考虑它们,但是对数据库进行版本控制并将数据吸收到其中似乎并不超出Clojure的可能性范围。
Polish
A lot of the good of AR comes from all the conventions for naming tables and naming columns, and all the convenience functions for capitalizing words and formatting dates and such. This has nothing to do with OO vs. non-OO; AR just has a lot of polish because a lot of time has gone into it. Maybe Clojure doesn't have an AR-class library for working with DB data yet, but give it some time.
AR的许多好处来自于命名表和命名列的所有约定,以及大写字母和格式化日期等的所有便利功能。这与OO和非OO无关;AR只是有很多的修饰,因为有很多时间。也许Clojure还没有用于处理DB数据的AR-class库,但是给它一些时间吧。
So...
Instead of having an object that knows how to destroy itself, mutate itself, save itself, relate itself to other data, fetch itself, etc., instead you have data that's just data, and then you define functions that work on that data: saves it, destroys it, updates it in the DB, fetches it, relates it to other data. This is how Clojure operates on data in general, and data from a database is no different.
而不是一个对象,知道如何摧毁自己,改变自己,拯救自己,与其他数据,获取本身,等等,而不是你有数据就是数据,然后在数据定义功能工作:保存它,摧毁它,更新数据库,获取它,与它的其他数据。这就是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})))
Something like that. It's inside-out from the way objects work, but it provides the same functionality. But in Clojure you also get all the benefits of writing code in an FP kind of way.
就像这样。它与对象的工作方式相反,但它提供了相同的功能。但是在Clojure中,您也可以从FP的方式中获得编写代码的所有好处。
#2
5
This question hasn't been answered in a while, but Korma http://sqlkorma.com is another project that also helps reduce the dissonance between SQL and Clojure. It's been worked on more recently and should work with more recent Clojure versions.
这个问题已经有一段时间没有答案了,但是Korma http://sqlkorma.com是另一个帮助减少SQL和Clojure之间不和谐的项目。它最近才被开发出来,应该可以用于更近期的Clojure版本。
#3
0
A new micro SQL DSL built upon clojure.contrib.sql has been started. It's on Github weighing in at a lightweight 76 loc.
一种新的基于clojure. managed b的微SQL DSL。sql已经启动。它在Github上,重量很轻,只有76 loc。
There's a lot of examples and theory behind the DSL on the author's blog, linked on his Github profile (new user SO hyperlink limit bah). Its design allows SQL to be abstracted into much more idiomatic Clojure, I feel.
作者博客上的DSL背后有很多例子和理论,链接到Github的个人资料(新用户SO超级链接限制bah)。我觉得,它的设计让SQL被抽象成更符合习惯的Clojure。