Regexp
などActiveRecordでメソッドが用意されていない標準SQLの演算子を清く正しく使う。
清く正しくとは、生SQL文字列を書かない程度の意味です。
下の方の長いやつはRails 4
までの話です
REGEXP
はRails 5
もしくはArel 7
からmatches_regexp
としてメソッドが用意されました。
Writer.where(Writer.arel_table[:email].matches_regexp('.*@gmail.com')) # SELECT "writers".* FROM "writers" WHERE ("writers"."email" ~ '.*@gmail.com')
Rails 5
になってなお下のような書き方をしていると殴られる可能性があります。
このようにどんどん便利になっていってるので、たまに知識更新するのも大切ですね。
たとえばRegexp
でwhere
する
グーグル先生にたずねるとStack Overflowが出てきて以下のようなコードが出てきます。
Writer.where("email REGEXP ?", '.*@gmail.com') # SELECT "writers".* FROM "writers" WHERE (email ~ '.*@gmail.com')
動きますが、せっかくのORMですから、ORMらしく書きましょう。
だいたいの演算子や句はArel
に用意されている
ドキュメントやグーグル先生より、ソース内を検索したほうが早いのですが、大体の演算子のビルダーが用意されています。
REGEXP
なら以下のように書けます。
Writer .where( Arel::Nodes::Regexp.new( Writer.arel_table[:email], Arel::Nodes::Casted.new('.*@gmail.com', Writer.arel_table[:email]) ) ) # SELECT "writers".* FROM "writers" WHERE "writers"."email" ~ '.*@gmail.com'
WHERE
句のカラム指定にテーブル名が入りました。
より具体的な記述になっていますが、テーブル名が欲しくない場合もあります('AS'した場合とか)。
ActiveRecord#arel_table
で得られるArel::Table
はその名の通りテーブルを前提とした値しか返してくれませんので、自分で組みたてる必要があります。
Writer .where( Arel::Nodes::Regexp.new( Arel.sql(Writer.connection.quote_column_name(:email)), Arel::Nodes::Quoted.new('.*@gmail.com') ) ) # SELECT "writers".* FROM "writers" WHERE "email" ~ '.*@gmail.com'
カラムのクォーティングのルールはデータベースによって違うので、quote_column_name
で万全を期すと良いでしょう。
値は値用にクォーティングします。
WHERE
句の()
が欲しい場合はさらにこうします。
Writer .where( Arel::Nodes::Grouping.new( Arel::Nodes::Regexp.new( Arel.sql(Writer.connection.quote_column_name(:email)), Arel::Nodes::Quoted.new('.*@gmail.com') ) ) ) # SELECT "writers".* FROM "writers" WHERE ("email" ~ '.*@gmail.com')
大変な騒ぎになってきましたね。
Arel
のルール
Arel
は安全性を維持するために、生のString
は受けつけてくれず、なんらかのArel
クラスでラップする必要があります。
ところでArel.sql
はArel::Nodes::SqlLiteral.new
のショートカットで、これは何の評価も加工もされず、SQLに渡ります。
カジュアルに使うと生SQLをいじくりまわしているのと大して変わらないので気をつけましょう。
長い
記述が長くなってうれしみが少ないですか?
最初はウッとなりましたが、自分で文字列を組みたてるのと比べると、これが別に気にならなくなったりします。
フシギですね。