Rails3のfind系メソッドと注意のまとめ

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使ったときのハマる具体例を追加しました.

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

morimorihoge

高校卒業後,学生をやりながらずっとWebアプリ開発に携わってきました.2010くらいまではPHP/Symfonyプログラマでしたが,それ以降のWeb開発はRailsほぼ一本に宗旨替えしました.開発とは別にサーバ構築・運用も10年以上やってきているので,要件定義から設計・実装・環境構築・運用まで一通り何でもこなせます.開発以外では季節により大学でWebサービス開発やプログラミング関連の非常勤講師もしており,技術の啓蒙・教育にも積極的に関わっています.最近はPM的な仕事が増えていますが,現役開発者としていつでも動ける程度にはコードもサーバも弄る日々を送っています.AWS 認定ソリューションアーキテクト – アソシエイトレベル取りました

morimorihogeの書いた記事

開発
RubyのArray(配列)の使い方

2017年03月15日

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ