加藤です。
あまり考えることなく、海外ではやっていたらそれをそのまま受け入れる「舶来信仰」タイプの人間なので、これまで国産DIコンテナにはピクリとも食指が動かなかった非国民な自分でありますが、"S2JDBC":http://s2container.seasar.org/2.4/ja/s2jdbc.html にはグッときました。
CoCは、「規約を守っておけばフレームワークが自動的に設定してあげる」というもので、CoCによって開発者は、あまりソースコードを書かなくてもすむようになります。 CoCは、確かに私たちを設定ファイル地獄から救ってくれました。
しかし、CoCにも暗黒面があります。ソースコードに明示されている部分が少ないので、 自動化されている部分がブラックボックスになり、 規約を知らない人が見ると何をやっているのかがまったくわからなくなってしまうのです。
また、規約を知らないと何もできなくなるので、ちょっとしたことでも、自分の知らないことであれば、 いろいろ調べたり試行錯誤を繰り返すことになります。このような試行錯誤の時間は馬鹿になりません。 最終的なソースコードは確かに少なくなったけど、 かかった時間は大して変わらなかったなんてことも十分にありえます。 自動化されているので最初はとっつきやすいのですが、知らないことやイレギュラーなことに弱いのです。
Railsがイレギュラーなことに弱いとは全然思わないけれど、"流れるようなインターフェース":http://capsctrl.que.jp/kdmsnr/wiki/bliki/?FluentInterface はイケてるので、早速ActiveRecordを使って真似してみました。が、書いた本人も使うつもりがないので以下は今のところネタです。タイトルを見てRailsプラグインの紹介だと思った方ごめんなさい。
ActsAsFluentedの使い方
まず、この下のファイルをRAILS_ROOT/libなどクラスパスの通ったディレクトリに設置します。
次に、environment.rbに1行追加します。
ActiveRecord::Base.send(:include, ActsAsFluented)
以上で、ActiveRecordモデルにacts_as_fluentedというクラスメソッドが追加されました。acts_as_fluentedが宣言されたモデルでは、with_query(operator = :all) {|query| block}というクラスメソッドが使えるようになります。
S2JDBCの説明で使われてた例と比較すると、ActsAsFluentedの使い方は以下のとおりです。
S2JDBC
List<Employee> results = jdbcManager.from(Employee.class)
.join("department")
.where("id in (? , ?)", 11, 22)
.orderBy("name")
.getResultList();
List<Employee> results = jdbcManager.from(Employee.class)
.join("department")
.where("id in (? , ?)", 11, 22)
.orderBy("name")
.getResultList();
ActsAsFluented
results = Employee.with_query :all do |q|
q.join :department
q.where("employees.id IN (?, ?)", 11, 22).order_by("name")
end
results = Employee.with_query :all do |q|
q.join :department
q.where("employees.id IN (?, ?)", 11, 22).order_by("name")
end
with_queryのoperatorパラメータでは、ActiveRecordのfindメソッドのように:allか:firstを指定することで結果をリストで取得するか単一で取得するかを指定できます。デフォルトは:allで省略可能です。また、ブロック内ではクエリをビルドしていくのですが、上の例のようにメソッドチェインでもDSLライクでもお好みで書くことが出来ます。
この説明からも判るとおり、実装はActiveRecordのfindメソッドに一皮かぶせただけなので、Railsを使っている方であればクエリの組み立て方は容易に想像がつくと思います。コードも40行もないので、もし使うのであれば一通り読んでからお使いください。
注意するポイントとしては、ActsAsFluented::QueryのjoinメソッドではActiveRecordのfindオプションの:includeを使って関連モデルを含めてロードします。同じクラスにjoinsメソッドもあるので紛らわしいです。あと、このActsAsFluented::Query#joinを使って関連モデルをロードした場合には、テーブル間で同じ名前のフィールドがあるとフィールド名だけではどちらか識別出来なくなるので、上記の例の用に、"employees.id IN (?, ?)"とテーブル名を指定する必要があります。フィールド名だけで一意に識別できるのであればテーブル名は不要です。
ActsAsFluentedのメリットは
前述したとおり、書いた本人が使おうと思っていないぐらいメリットは感じられないのですが、しいてあげれば、
- メソッド化した方が、打ち間違えた時にoptionsハッシュより発見しやすいかも
- よりSQLに近い書き方ができるようになる
といったところでしょうか。メソッド化することによりIDEなどでコード補完が効くようになるS2JDBCに比べると全然アピールしませんね。何か他に付加価値をつけることができれば、正式にプラグインとして開発したいのですが思いつきません。
ところで、ORマッパーの流行り廃りには僕自身何度も振り回されてきましたが、今はActiveRecordで満足しています。僕が関わるアプリケーションで実行速度がシビアに要求されない99%の場面では、これで充分です。
でも、残り1%の場面は必ずおとずれるし、その1%にほとんどの労力が割かれた苦い経験もあるので、最後に将来に備えて自分なりに考えた各パターンの使いどころをまとめてみました。
- 「プログラマーなんかにSQLは触らせねぇ」というDBAがいる場合は名前付きクエリで外出し
- HibernateのQueryインターフェイスは詳細検索のような動的クエリを組み立てるのに便利
- フレームワークに不慣れな開発者をIDEの機能でサポートしたい場合は「流れるようなインターフェイス」
- でも、やっぱりActiveRecord
ということで、今日も find, save & destroy.