読者です 読者をやめる 読者になる 読者になる

ActiveRecord の named_scope に by_<column_name>みたいな名前つける人間は腹を切って死ぬべき……だよね?

最近仕事で ActiveRecord ばかり触ってるのでその話題

ActiveRecoedには named_scope という便利な機能があって、これはまあ動的にクエリを組み立てるのに便利で良いよねって話がまあよくありますね。その話をします

最初に、Railsの嫌なところの愚痴

クエリビルダー死すべきみたいな話はあると思うし一理あると思うんだけど Rails 使う以上 AR 使わないとやってられんみたいなのもあるので AR 内でのベストプラクティスでやるしかない。こういうあたり自由じゃないなーって感じがしてわたしは PerlTIMTOWTDI な感じがやっぱり好きだなーってなるんだけど、まあその話はおいておく。

named_scope は便利って言われるけど何が便利なの

今回は別に named_scope ってのは便利だねってことに関して異を唱えるつもりはなくて、named_scope が嬉しいのはなんでなの? と言う話をしたい。メリットはなんなのかという話だ。なんか scope を chain させていくと動的にクエリが組み立てられて便利!!!しかも遅延評価だから必要になるまでクエリ発行されない!!! みたいなメリットが目立ってる感じするんだけど、それは別に named_scope に限った話じゃなくて普通に where を chain させたっていいし scoped をchain させたって同じことができる。

じゃあ named_scope の何がすばらしいのかというと、わたしが思うに二つある。

ひとつめ

ひとつは、集合(RDB内のレコードたちは集合の一種だ)を絞り込む、その「やりかた」を隠蔽した上で名前をつけられることで、実装を隠蔽して名前をつけるってのは要するに手続きの抽象化だ。たとえば以下のようなものを考えてもらえばいい。

class Player < ActiveRecord::Base
  named_scope :top, lambda{|n|
    {:order => "score DESC", :limit => n}
  }
end

Player.top(3)
# SELECT * from players ORDER BY score DESC LIMIT 3

中ではあるカラムをもとにレコードを整列させて上からn件取ってくるということをやっているんだけど、使う側はそれを外から意識しなくてよくなる。テーブル構造のことを(理想的には)意識しなくていい。これは手続きの抽象化だ。で、これをchainさせていくとデータのロードは遅延されるので、手続きが抽象化された上で、無駄なロードも発生しない(しにくい)。Player.age_under(18).top(3) みたいなことも、 テーブル構造を意識しないまま、無駄なクエリなくできるわけだ。

ふたつめ

もうひとつのすばらしいところは、named_scope で絞り込んだ集合に対する操作を scope 内に定義できることだと思う。これに関しては今回は書かない。

抽象化されないならメリットない(あるいは半減する)

今ここで問題にしたいのはひとつめのメリット、手続きを抽象化(実装を隠蔽)できるのがすばらしいと言う話について。で、ここでようやくこの記事のタイトルが出てくるんだけど、named_scope の利点(のひとつ)は「中でどういうテーブル操作してるか」を見なくていいことであって、だから named_scope に by_<column_name> みたいな名前をつけるのはクソなんじゃないかなー。それはテーブル構造を抽象化できてない。 scoped(:conditions => {:column_name => }) してるのと変わらない。実装がむき出しになってる。だったらそれ named_scope じゃなくてもいいよね、適材適所で使えてないよね。あと、余談として、scope1_and_scope2(param1, param2)みたいな命名も、何も抽象化してないから意味ないよね。だったら普通に scope1(param1).scope2(param2)みたいにすればいいじゃん。

この記事の言いたい事

というわけで、ほんらい named_scope は「どのような意味を持ったサブ集合に注目(scope)するのか」という観点で命名、定義すべきであって、「どうやってクエリを発行するのか」という観点から命名、定義するべきではないんじゃないの、chainできて便利ー遅延評価で便利ーみたいなのはちょっと見るべきポイントがズレてるんじゃないの、というのが、この記事で問題提起したいことです。

最後に言うまでもない余談

余談として、言うまでもないけど、named_scopeの中身、実装の部分では、実行効率と保守性の観点からどのようなSQLを発行するべきなのかもきちんと考えるべき(というかそれが本分)だと思います。SQLRDBMS わかんないならそもそもO/Rマッパーも使えるはずない、という気持ちも持つべきだと思う。