いつの間にかRuby on Rails 2.1がリリースされていました。
これは試さねばと、gem update rails -yと実行するとrails 2.1.0がお目見え。とりあえず、他のリリースノートを見ていくと、named_scopeという新機能。・・・こ、これは!

さようならbuild_condition

私の場合、これまでは検索条件は各コントローラでbuild_conditionというメソッドを用意し、そこで管理するという実装ポリシーでした。例えばこんな感じ。


def build_condition
    conditions = Array.new
    if (params['price'] && params['price'] != '')
        conditions << "products.price < '#{params['price']}'"
    end
    conditions.join(' and ')
end

まぁ、複数条件を指定したい場合や、修正箇所を1か所にできるなどを考えて、美しくないと思いながらもこのような実装をしていました。このコードは例えば任意の価格未満の商品を探すといったものです。
しかし、実際は10,000円未満の商品は?といった探し方、つまり価格帯で探すのが、世のオンラインショッピングにおけるUIパターンの基本です。

10,000円未満の商品が欲しい

この要望をnamed_scopeによって実現します。まずは、model/product.rb


named_scope :below_10000, :conditions => ["products.price < 10000"]

#controllers/products_controller.rbでの呼び出し
@products = Product.below_10000

商品名でキーワード検索したい


named_scope :by_keyword, lambda {|*args|
    {:conditions => ["products.name like ?", '%' + args.first + '%']}}

#controllers/products_controller.rbでの呼び出し
@products = Product.by_keyword("Rails")

デフォルトの並びは商品の新着順にしておきたい


named_scope :order_default, :order => "products.created_at desc"

#controllers/products_controller.rbでの呼び出し
@products = Product.order_default

10,000円未満の商品を新着順に並べたい


@products = Product.below_10000.order_default

す、スマートすぎる・・・MVCアーキテクチャにおいて、知りあい関係をどう分担するかが肝となりますが、RailsにおけるModelはO/RマッピングにおけるEntityつまりDB上の1テーブルに対する実態とDAOの基本機能という存在でした。
結局のところ、検索・登録・更新・削除といった基本メソッドをもったところで、サービスの中心である「検索条件」という点についてはコントローラ側の役割と私は解釈していました。

しかし、これでControllerの役割であるユーザ入力からModelへのデータ引き渡しという本来の姿により近くなることができました。Modelの役割が増えたと考えるか、DAOとしての機能がModelに収まるので分業化が楽と考えるかはMVCアーキテクチャに対する解釈によりけりでしょうね。
ただ、実装側からすれば、より直観的な方法で実装できる分、嬉しいですね。

#2008/06/06 追記
orderに関する記述とconditionsに関する記述の順番が入れ替わっても平気だった


@products = Product.order_default.below_10000

ここはLinqより頭いいです。素晴らしい。

#2008/06/08 追記
:includeと:conditionsが併用できました。申し分ありません。


named_scope :by_genre, lambda {|*args|
    {:include => [:genres], :conditions => ["genres.uri = ?", args.first]}}

ほぼ完全にSQLをcontrollerに記述しなくてよいです。modelはDAOとvalidationがメインということで大分すっきりしました。