【Rails】findとfind_byとwhereと時々オトン

想定読者

  • find, find_by, whereの使い分けがよくわからない人

findメソッドの概要

  • 各モデルのidを検索キーとしてデータを取得するメソッド
  • id以外の条件で検索不可
  • 戻り値は検索対象のクラスのインスタンス
  • 取得したいデータのidの値が、1、10など**特定されている場合**に使用
# 単一のIDを指定
pry(main)> Customer.find(1)
Customer Load (0.4ms)  SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

# 複数のIDを指定
pry(main)> Customer.find(1, 2, 3)
Customer Load (8.7ms)  SELECT "customers".* FROM "customers" WHERE "customers"."id" IN ($1, $2, $3)  [["id", 1], ["id", 2], ["id", 3]]

# 配列で複数のIDを指定
pry(main)> Customer.find([1, 2, 3])
Customer Load (3.5ms)  SELECT "customers".* FROM "customers" WHERE "customers"."id" IN ($1, $2, $3)  [["id", 1], ["id", 2], ["id", 3]]
  • エラー時の戻り値(検索対象が存在しなかった場合)は例外を返す
  • もう一度いう。検索対象が存在しなかった場合は例外を返す
pry(main)> Customer.find(189879)
Customer Load (1.5ms)  SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 LIMIT $2  [["id", 189879], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find Customer with 'id'=189879

find_byの概要

  • 各モデルをid以外の条件で検索するメソッド(idでも検索可能)
  • 複数の検索条件を指定可能
  • 戻り値は検索対象のクラスのインスタンス
  • 返ってくる結果は、最初にヒットした1件のみ
  • id及びid以外の条件が分かっている場合、その条件に該当する最初のデータを取得したい場合に使用
pry(main)> Customer.find_by(name: "株式会社ミライ・トラスト")
Customer Load (7.2ms)  SELECT "customers".* FROM "customers" WHERE "customers"."name" = $1 LIMIT $2  [["name", "株式会社ミライ・トラスト"], ["LIMIT", 1]]
  • エラー時の戻り値(検索対象が存在しなかった場合)はnilを返す
pry(main)> Customer.find_by(name: "株式会社ミライトラスト")
Customer Load (34.7ms)  SELECT "customers".* FROM "customers" WHERE "customers"."name" = $1 LIMIT $2  [["name", "株式会社ミライトラスト"], ["LIMIT", 1]]
=> nil

whereの概要

  • 各モデルをid以外の条件で検索するメソッド
  • 該当するデータ全てが返ってくる
  • 戻り値はActiveRecord::Relationオブジェクト
# 前方一致
pry(main)> Customer.where('address like ?', '東京都')
Customer Load (41.9ms)  SELECT "customers".* FROM "customers" WHERE (address like '東京都')

# 後方一致
pry(main)> Customer.where('name like ?', '株式会社%')
Customer Load (71.4ms)  SELECT "customers".* FROM "customers" WHERE (name like '株式会社%')

# 部分一致
pry(main)> Customer.where('name like ?', '%アイドマ%')
Customer Load (34.8ms)  SELECT "customers".* FROM "customers" WHERE (name like '%アイドマ%')

# and検索
pry(main)> Customer.where('name like ?', '%アイドマ%').where('address like ?', '東京都')
Customer Load (34.8ms)  SELECT "customers".* FROM "customers" WHERE (name like '%アイドマ%')
  • エラー時の戻り値(検索対象が存在しなかった場合)も要素0個のActiveRecord::Relationオブジェクト
pry(main)> Customer.where('name like ?', '%アイドマーーー%')
Customer Load (92.5ms)  SELECT "customers".* FROM "customers" WHERE (name like '%アイドマーーー%')
[]


まとめ

  • リソースが特定されているCRUD処理では基本的にfindを使用

→例えばリソースの更新時、削除時にfindbyでidを指定することで同様にリソースを検索することは可能だが、findbyの場合エラー時の戻り値はnilとなり、findのように適切な例外クラス(RecordNotFound)が上がらない。find_byでエラー時にnilが返ることにより、UndefindMethodやActionController::UrlGenerationErrorなど実際のエラー内容とは異なる例外が上がり、ログ監視ツール上でもHTTPステータスエラーとしては本来404で上がってくるべきところが500番台(InternalServerError)などが返されたりしてエラーの特定に混乱を招いたりもしかねない。

  • リソースが特定されているCRUD処理のうち、findでうまく引っ掛けられない場合にはfind_byを使用(あまり想定できないが)
  • リソースが特定できていない、複数のリソースをまとめて取得したい場合はwhereを使用