🐯

【Rails】find_byとwhereの違い

2023/03/15に公開

find_byは条件に合致した最初のレコードを返す

book = Book.find_by(title: 'hoge')

> book.class
=> Book(id: integer, title: string, already_read: boolean, created_at: datetime, updated_at: datetime)

find_byのAPIリファレンス:

Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil.
https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_by

発行されるSQL:

> Book.find_by(title: 'hoge')
Book Load (0.3ms)  SELECT "books".* FROM "books" WHERE "books"."title" = ? LIMIT ?  [["title", "hoge"], ["LIMIT", 1]]

whereActiveRecord::Relationオブジェクトを返す

whereActiveRecord::Relationオブジェクトを返す。

# hogeというタイトルのBookが複数ある想定
books = Book.where(title: 'hoge')

> books.class
=> Book::ActiveRecord_Relation

whereのAPIリファレンスを見ると、レコードではなく「新しいrelationを返す」と書かれている。

Returns a new relation, which is the result of filtering the current relation according to the conditions in the arguments.
where accepts conditions in one of several formats. In the examples below, the resulting SQL is given as an illustration; the actual query generated may be different depending on the database adapter.
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where

あくまでもActiveRecord::Relationオブジェクトが返るので、この時点でまだレコードは返していない(まだSQLが発行されていない)。

# 発行されるSQL
> books.to_sql
=> "SELECT \"books\".* FROM \"books\" WHERE \"books\".\"title\" = 'hoge'"

よって、関連付けされたオブジェクトを取得するといったことはできない。
たとえば、Book has-many Reviewの関連がある場合、NoMethodErrorが発生する。

> books.reviews
/Users/kanazawa/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/activerecord-7.0.4.3/lib/active_record/relation/delegation.rb:110:in `method_missing': undefined method `reviews' for #<ActiveRecord::Relation 
[#<Book id: 2, title: "hoge", already_read: nil, created_at: "2023-03-15 08:25:13.159646000 +0000", updated_at: "2023-03-15 08:25:13.159646000 +0000">, 
#<Book id: 3, title: "hoge", already_read: nil, created_at: "2023-03-15 08:37:33.053312000 +0000", updated_at: "2023-03-15 08:37:33.053312000 +0000">]> (NoMethodError)

whereで返るのがActiveRecord::Relationオブジェクトなので、上記を無理やり実現するならbooks.firstなどとレコードを指定する必要がある(が、あまりやらないと思う)

# ここで初めてSQLが発行されているのがわかる
> books.first.reviews
  Review Load (0.1ms)  SELECT "reviews".* FROM "reviews" WHERE "reviews"."book_id" = ?  [["book_id", 2]]
=> []

初歩的な内容だけど大事なこと🐯

ActiveRecord::Relationオブジェクトについては、osyoさんがKaigi on Railsで発表した解説がわかりやすい。
ActiveRecord::Relation ってなに? by osyo - Kaigi on Rails 2022

Discussion