4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Squeelの列名を動的に指定する

Posted at

Squeelを使うと、Rubyの枠内で複雑なクエリもすいすい書けるようになりますが、さらに凝ったことをしようとするとひと工夫が必要になります。

Squeelって?

Railsでちょっと複雑なSQLのクエリを書こうとすれば、一部を文字列にするか、Arelを使うかということになりますが、どちらにも使いづらい点があります。

  • 文字列で書いてしまうと、あとあとの再利用が不便になる
  • Arelは内部向けのAPIであって、使いにくい&仕様が安定していない

ここで登場するのがSqueelです。SqueelはArelをラップして、Ruby的なDSLでSQLを書けるようにしてくれます。使い方については他のQiita記事タイムインターメディアさんの記事に詳しいです。

動作を考える

たとえば、「身長が160cm以上、体重が70kg以下」というような条件で検索をかけるとなると、こんな感じになります。

DSL
Person.where{(height >= 160) & (weight <= 70)}

さて、heightweightなんて定義した覚えはないのですが、いったいどうなっているのでしょうか。whereの引数がブロックとなっていることからも予想できるかもしれませんが、じつはこのブロック全体がinstance_evalで評価されます。そして、存在しない名前についてはmethod_missingが拾っていって、評価式を組み立てるためのオブジェクトに変換してしまう、という仕組みになっています。

動的に指定…できる?

さて、ちょっと凝ったことをしたくなったので、Squeelで条件とする列名をハードコードするのではなく、シンボルなどで外から与える必要が出てきました。ただ、呼ぶべきものは明示的なメソッドがあるわけではない、method_missing上の機能なので、一体どうすればいいのでしょうか。

と思って調べてみると、sendでメソッドを呼んだ場合にも、当該メソッドがなければmethod_missingに回ることが判明しました。ということで、DSL内部でsendを使ってみることにしました。

失敗例
column = :height
Person.where{send(column) >= 160}

とりあえず実行時にエラーは起きなかったのですが、これでDBを参照してみるとSQL段階でエラーとなってしまいました。原因を調べるために.to_sqlとしてみると、SELECT (中略) WHERE send(height) >= 160というように、sendがDB関数だと解釈されてしまっていました。

非常用メソッド

ということで、この環境ではsendすら削除されてmethod_missingに流れるようになってしまっていました。ただし、sendにはもう1つの名前である__send__というのがあって、これはBasicObjectにすら用意されているものです。こちらでしてみると、どうなるでしょうか。

成功例
column = :height
Person.where{__send__(column) >= 160}

こうすることで、きちんと本来のメソッドまでコードが回るようになりました。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?