現在、いくつかの ORM に似たライブラリが開発中です。
メーリング リストでは、最近、一部の (賢い) 人々が、これがどのように機能するかについて他のモデルについて説明しました。これらのライブラリはそれぞれ、問題に対してかなり異なるアプローチを取っているため、必ずそれらすべてを確認してください。
たとえば、Oyako を使用した拡張例を次に示します。このライブラリは本番環境に対応しておらず、まだ大規模な開発が行われているため、この例は 1 週間で無効になる可能性がありますが、それは達成されつつあります。いずれにせよ、それは概念実証です。しばらく待つと、誰かが良いライブラリを思い付くでしょう。
clojure.contrib.sql
DB から (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
バーをリストするキーがあります。
親子では、「データマップ」はあくまでも地図です。クエリ自体はマップです。返されるデータは、(マップのベクトルの) マップのベクトルです。したがって、これらすべてを構築、操作、および反復するための標準的で簡単な方法が得られます。これは素晴らしいことです。これらのマップをより簡単に作成および操作できるように、いくつかの砂糖 (マクロおよび通常の関数) を追加すると、非常に強力になります。これは一例にすぎず、多くのアプローチがあります。
別の例として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 [& _]) ;; 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 には、DB データを操作するための 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})))
そんな感じ。オブジェクトの動作は裏返しですが、同じ機能を提供します。しかし、Clojure では、FP のような方法でコードを書くことのすべての利点も得られます。