【Rails】find_byとwhereの違い
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]]
where
はActiveRecord::Relation
オブジェクトを返す
where
はActiveRecord::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