Rails3では,ActiveRecordを使ったデータの取得方法にfind
, find_by_XXX
, find_all_by_XXX
, all
, where
での絞り込みなど色々ありますが,それぞれ微妙に異なった挙動をします.
ちょろいサンプルアプリを書く位ならあまり気にならない所なのですが,きちんとアプリを書く上では以外と大事なところ(後述)なので,ここいらでまとめておきます.
Rails使っている人で
hoge = Hoge.find(params[:id]) || Hoge.new
とか書いて思った様に動かないぞ?とかハマった人向けです.
取り上げるのはfind
, find_by_XXX
, all
, find_all_by_XXX
, where
です.Railsのバージョンは3.2.8を使っています.
以下の説明では,以下のクラスが存在することを前提とします.
class Hoge < ActiveRecord::Base
end
ActiveRecord#find
Railsデフォルトのscaffoldが使うfindです.
hogesテーブルからID: 111のレコードを取得する.
hoge = Hoge.find(111)
- 当該IDのレコードが存在する場合,当該クラスのインスタンス(この場合はHoge)を返す
- 当該IDのレコードが存在しない場合,
ActiveRecord::RecordNotFound
をraiseする.この例外をcontroller側でrescueしない場合,404エラーになる
ActiveRecord#find_by_XXX
1レコード取り出したい場合に便利なメソッドです.XXXはフィールド名になり,後ろに_and_YYYとどんどん条件を追加していくことが可能です.ORDER BY id DESC
を付けて検索が行われるため,複数レコードがある場合は最後にINSERTされたレコードが返ります.
hogesテーブルからID: 111のレコードを取得する.
hoge = Hoge.find_by_id(111)
- 当該IDのレコードが存在する場合,当該クラスのインスタンス(この場合はHoge)を返す
- 当該IDのレコードが存在しない場合,nilを返す
nilを返すため,その後にメンバ変数にアクセスする場合には,nilチェックしないとmethod not foundが出ます.
ActiveRecord#all
当該テーブルの全レコードを取り出します.scaffoldの#index
で使われています.
hogesテーブルから全てのレコードを取得する.
hoges = Hoge.all
- 当該クラスのインスタンスが入ったArrayを返す
- レコードが存在しない場合,空のArray []を返す
all
はその時点でSQLを発行してArrayを返すため,後ろにwhereやorder条件を付けることができません.割と不便.
ActiveRecord#find_all_by_XXX
allの条件指定版です.
hogesテーブルからID: 110, 111のレコードを取得する.
hoges = Hoge.find_all_by_id([110, 111])
- 当該クラスのインスタンスが入ったArrayを返す
- レコードが存在しない場合,空のArray []を返す
all
と似てますね.
ActiveRecord#whereを始めとするArel系メソッド
where
を使ってfind系と同じようなことをすることは可能なので,参考までに違いを挙げておきます.
hogesテーブルからID: 111のレコードを取得する.
hoge = Hoge.where(:id => 111).first
# prepared statement風に
hoge = Hoge.where("id = ?", 111).first
# joinがある場合はテーブル名を指定して以下の様にする.scope切るときはテーブル名指定した方が無難
hoge = Hoge.where("hoges.id = ?", 111).first
hogesテーブルからID: 110, 111のレコードを取得する.
hoges = Hoge.where(:id =>[110, 111])
hoges.each do |h|
# hに対する操作
end
# prepared statement風に
hoges = Hoge.where("id IN (?)", [110, 111])
# joinがある場合はテーブル名を指定して以下の様にする.scope切るときはテーブル名指定した方が無難
hoges = Hoge.where("hoges.id IN (?)", [110, 111])
where
そのものはActiveRecord::Relationを返すActiveRecord::Relation
はEnumerableに似たアクセスができる(#each
とか#first
とか#last
とか)ActiveRecord::Relation
は遅延評価されるので,レコードが必要になるまでクエリは投げられない(Viewのレンダリング中にクエリが発行されることがある)ActiveRecord::Relation
にはさらにwhere
やorder
などを繋いで行くことができる.2回以上where
を呼べばAND条件で結びつけられる
ActiveRecord::Relation
を使った方が集合を取ってくるときには便利です.ただ,注意として,prepared statement風にINを使って書く場合,引数が空配列になると「WHERE id IN (NULL)
」で実行されてしまうので,NULL許可しているフィールドにWHERE INを使う場合には注意が必要です.
以下,参考までにそれぞれのwhere引数と,どんなSQLが実行されるかをまとめておきます.
# SELECT `hoges`.* FROM `hoges` WHERE `hoges`.`id` = 111 ORDER BY id DESC
Hoge.where(:id => 111)
# SELECT `hoges`.* FROM `hoges` WHERE `hoges`.`id` IN (111) ORDER BY id DESC
Hoge.where(:id => [111])
# SELECT `hoges`.* FROM `hoges` WHERE `hoges`.`id` IN (111, 112) ORDER BY id DESC
Hoge.where(:id => [111, 112])
# SELECT `hoges`.* FROM `hoges` WHERE (id = 111) ORDER BY id DESC
Hoge.where("id = ?", 111)
# SELECT `hoges`.* FROM `hoges` WHERE (id IN (111)) ORDER BY id DESC
Hoge.where("id IN (?)", [111])
# SELECT `hoges`.* FROM `hoges` WHERE (id IN (NULL)) ORDER BY id DESC
Hoge.where("id IN (?)", [])
# SELECT `hoges`.* FROM `hoges` WHERE (hoges.id IN (111)) ORDER BY id DESC
Hoge.where("hoges.id IN (?)", [111])
どんなときにハマるか?
例としては,漫然と
hoge = Hoge.find(params[:id]) || Hoge.new
というコードを書いたときにハマります.このコードの意図は「params[:id]
のレコードが無ければHogeのnewしたインスタンスをhogeに代入する」ですが,実際に動かしてみるとparams[:id]
のレコードが無い場合,404エラーになります(前述の通りActiveRecord::RecordNotFoundがraiseされるため).
この場合,
hoge = Hoge.find_by_id(params[:id]) || Hoge.new
と書かなければいけないんですね.この辺,割とハマることが多いので注意ですね.
※10/19 find
使ったときのハマる具体例を追加しました.