ArelでLIKEとかいうRails
同じ名前のカラムがあるモデルに関連があるとする。FooとBarがあってそのどちらにもname属性があるようなケース。
Foo.where(:name => 'baz')
これはもちろん問題なく実行できる。次のような関連を使う場合でも特に問題は起きない。
Foo.joins(:bar).where(:name => 'baz')
ここでハッシュ表現できないような条件を考えてみる。
Foo.where('name like "baz"')
これももちろん問題ないのだけれど、次のようなのになるとちょっと困ってしまう。
Foo.joins(:bar).where('name like "baz"')
もしもこれを実行しようとするとStatementInvalid例外が上がってくる。SQL文を生成させてみると「WHERE (name like "baz")」となってしまっているのを確認できる。
whereメソッドで指定するSQL片の書き方を変えればこの状況を回避できる。ただ、ここでテーブル名を持ち出すというのには引っかかりあって、これはどうにかならないものかと思ってしまう。table_nameメソッドを使うなどしてテーブル名を書かないようにしてみたりもするが、それも少し引っかかる。
ある時、Rails 3では次のように書けるというのを知った。
t = Foo.arel_table Foo.joins(:bar).where(t[:name].matches('baz'))
こちらで生成されるSQL文では「WHERE (foos
.name
LIKE 'baz')」のようになり、上のような例外は発生しない。
これがArel。Arelを使うと、この他にも、たとえば「or」なんかもSQL直書きをせずにすませられる。次のようにかなり強力である。
Foo.where(t[:name].eq('foo').or(t[:name].eq('bar'))).order(t[:name].asc)) #=> ... WHERE ((`foos`.`name` = 'foo' OR `foos`.`name` = 'bar')) ORDER BY `foos`.`name` ASC
実際、Arelは新しくなったActiveRecordを支えているものの一つであって、Rails 3でおなじみになった次のような書き方もこのArelのレイヤで可能となっている。
c1 = t[:name].eq('foo') c2 = t[:name].eq('bar') Foo.where(c1.or c2)
もっともこれらは例のための例といったものでもあって、このような単純なケースであれば次のように書いてしまったほうが早いかもしれない。
Foo.where(t[:name].eq_any %w(foo bar))
詳しくはRuby Freaks Lounge第43回 Rails 3を支える名脇役たち その1 - Arel -を。どんなメソッドがあるのかについては今のところソースコードを見るのかな。(ところでmatchesで生成されるLIKEへのESCAPEを指定する方法はあるのだろうか。)