Railsの.whereに渡せるもの


RailsのActiveRecordで条件を絞るメソッドといえば.whereですが、いったいどれだけの形式の値を渡せるのでしょうか。

なお、手元にあったという理由でRails 4.2.7.1で検証を行っています。

whereの定義場所と、処理の流れ

まずは、ActiveRecord内部でのwhereの処理を追いかけてみることにします。

where自体はActiveRecord::Relationのメソッドですが、実体はinclude元のActiveRecord::QueryMethodsにあります。どのように処理が進むのか、流れを辿ってみました。

  1. 引数がハッシュだった場合、(必要なら)JOIN先のreferencesをセットしておく
  2. build_whereで条件を整えておく
  3. where_valuesに整えたものを追加

whereを実行した段階ではここまでですが、実際に取り出す段となると、以下のように処理が動いて、クエリが走ります。

  1. ActiveRecord::Relation#loadでリレーションのロードを起動する
  2. ActiveRecord::Relation#exec_queriesでクエリの処理を開始する
  3. ActiveRecord::QueryMethods#build_arelで、今までに積み重なったwhere_valuesをArelに起こす
  4. find_by_sqlでクエリを投げる
  5. Eager load関係の処理

ということで、各所でどのような処理が行われるか追いかけていく必要があります。

StringArray

これらの場合は、与えられたパラメータがActiveRecord::Sanitization::ClassMethods#sanitize_sql_arrayに渡ります(1つ、あるいは複数の文字列を配列で渡しても、まとめて配列で渡す形となります)。そして、以下のどれかのパターンで引数をエスケープして代入する処理が行われます。なお、1番目に来たパターン自体には何もエスケープが入りませんので、むやみに変数代入したりしてしまわないように気をつけましょう。

  • :name形式で埋めたものを、2番めに渡したハッシュの同じキーに入った値で置き換え
  • ?の入った箇所を、後に続く配列の値で順に置き換え
  • '%s'のような文字列を入れておいて、sprintfで置き換え(※単純な文字列置換で、クオートされないので要注意)

なお、上2つの場合、ActiveRecord::Relationを渡すとSQLに変換して埋め込む、なんて機能もあるようです(うまく使わないとややこしそうですが)。

Hash

引数にハッシュを渡すと、ActiveRecord::PredicateBuilder.build_from_hashが呼び出されて、キーごとに処理が進んでいきます。ハッシュの値の型によって、どんな処理になるかが違ってきます。

  • スカラー値…完全一致(nilIS NULLに変換)
  • モデルオブジェクト…idの一致
  • Rangeオブジェクト…範囲検索(なお、..BETWEENに変換されますが、...もきちんと不等号で再現してくれます)
  • ActiveRecord::Relation…指定された列(指定がなければ主キーの列)をSELECTした結果をサブクエリにしたINクエリ
  • 配列…配列の要素によって、
    • スカラー値…nil以外全部まとめてINnilは別途でIS NULLにしてくれる)
    • モデルオブジェクト…idの一致
    • Rangeオブジェクト…範囲検索
  • ハッシュ…JOINしたテーブルへの条件(ハッシュの中身は親のハッシュと同様)

その他(Arelノード)

build_whereでは、これまでに挙げなかった型の変数が来てもそのまま通すようになっていますが、あとでbuild_arelしてエラーにならないもの、ということで、事実上はArelノードに限られます。