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をいじくりまわしているのと大して変わらないので気をつけましょう。
長い
記述が長くなってうれしみが少ないですか?
最初はウッとなりましたが、自分で文字列を組みたてるのと比べると、これが別に気にならなくなったりします。
フシギですね。