As the title suggests, I used find_by in scope when I was writing Rails for business.
So, if the conditions are not met, I encountered an event that ** returns all records in the corresponding table instead of nil
**, so I will leave the solution and cause as a memorandum.
The answer is in the documentation. Active Record Query Interface (https://railsguides.jp/active_record_querying.html) ~~ Search did not find similar articles. Is it because it is written in the document? ~~
That way, if none match the criteria, nil
will be returned instead of all records.
product.rb
# bad
scope :find_by, ->(product_id) {
find_by(product_id: product_id)
} #=> ActiveRecord::Relation
# good
def self.find_by(product_id)
find_by(product_id: product_id)
end #=> nil
To illustrate that, let's look at the difference between scope and class methods.
Rails Guide 14 Scope (https://railsguides.jp/active_record_querying.html#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97) I will quote from the official.
By setting the scope, you can specify commonly used queries that are referenced as method calls to related objects and models. The scope can use all the methods that have appeared so far, such as where, joins, and includes. Every scope method always returns an ActiveRecord :: Relation object. You can also make other method calls to this object that contain different scopes.
To summarize what I want to say, the scope always returns a ** ActiveRecord :: Relationobject. ** ** Returns a
ActiveRecord :: Relation object even if the search result is
nil`. Absolutely. definitely! !! ~~ I said it twice because it's so important. ~~
I have a good article about ActiveRecord :: Relation
that is easily organized, so I will quote it.
What is ActiveRecord :: Relation?
I will summarize from the above article.
If there is a model called Product
, the one returned by Product.all
or Product.where (name:" hoge ")
is an instance of ActiveRecord :: Relation
.
Roughly speaking, ActiveRecord :: Relation
is the one with multiple records (instances?).
If you do where.class
, you can see that Product :: ActiveRecord_Relation
is returned.
find_by.class
returns the Product
class itself.
[31] pry(main)> Product.where(id: 1).class
=> Product::ActiveRecord_Relation
[32] pry(main)> Product.find_by(id: 1).class
Product Load (2.1ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1
=> Product(id: integer, name: string)
The article below was very easy to understand, so let me quote it. Returning nil in ActiveRecord scope ... Class methods work exactly with scope. (Scope is not an instance method. ~~ I misunderstood here. ~~)
The operations performed by the following two are almost the same. The only difference is the return value for nil
and User.none
.
user.rb
class User < ActiveRecord::Base
scope :hoge, -> (fuga) { find_by(fuga: fuga) }
end
class User < ActiveRecord::Base
def self.hoge(fuga)
find_by(fuga: fuga)
end
end
The reason it returns all records in scope is that it always returns a ActiveRecord :: Relation
object. ** **
It always returns ActiveRecord :: Relation
, so if it is nil, it will automatically return all records.
If you use a class method, nil
is returned because it behaves as it should.
This is the answer.
Scope-kun "Yeah, access the DB and narrow down the records ~"
⬇️ SQL execution
Scope-kun "What !? There is no match, nil
... "
Scope-kun "But Wai has decided to return an absolute ActiveRecord :: Relation
object ... "
Scope-kun "Hmm, if you return nil
, it's a violation of the rules, and it can't be helped, so return all the records! "
I think that something like that is unfolding. Lol (If you execute the same SQL that was executed in scope, it will be displayed as 0 when it is nil.)
I didn't know anything about ActiveRecord :: Relation
, so when I first encountered this event, I thought," It's a Rails bug !? " ~~ I'm sorry to doubt Rails. It was a crunchy specification. ~~
I think it's a rare case that none of the records match in find_by
, but I'd be happy if you could put such an event in the corner of your head.
find_by
haswhere (wheres) .limit (1)
in terms of internal movement in the first place.
You can reproduce find_by
by calling the take
method after narrowing down by where
.
[32] pry(main)> Product.where(id: 1).take.class
Product Load (2.1ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = 1 LIMIT 1
=> Product(id: integer, name: string)
Depending on the situation, there may be situations where where + take
is more appropriate (such as when you wantnil
to be returned).
The confusing thing here is that if where + take
does not hit any records in the scope, all the records in the corresponding table are also returned.
nil
is returned + Scope is due to the specification that ActiveRecord :: Relation
is returned.
As an aside, the take!
Method throws a ActiveRecord :: RecordNotFound
exception, so it looks like you can substitutefind_by!
.
I've written this for a long time,
If there is a possibility that there is no match in find_by
, it is better to use thefind_by!
Method and throw the ActiveRecord :: RecordNotFound
exception for error handling and processing is better?
(In the first place, it seems abnormal that the find_by
does not match)
# product.rb
scope :hoge, ->(product_id) {
find_by!(product_id: product_id)
}
# product_controller.rb
def fuga
product = Product.hoge(params[:product_id])
render json: product, status: :ok
rescue ActiveRecord::RecordNotFound => e
render json: e, status: :not_found
end
Recommended Posts