March 2, 2021
This blog is part of our Rails 7 series.
There were no methods defined to find and assert the presence of exactly one record at the same time.
For example, we have a class Product with a price
field and we want to find a
single product that has a price of 100. For zero or multiple products with the
price of 100, we want to raise an error. We can not add database constraints to
make a unique field of price
.
Now to solve the above query, we don't have any method defined in
ActiveRecord::FinderMethods
module. We can find a product with the given price
or raise an error if no record is found using the queries mentioned in the below
example.
Product.find_by!(price: price)
#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price)
#=> #<Product ...> (first product with the given price)
Product.where(price: price).first!
#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price)
#=> #<Product ...> (first product with the given price)
We can only use database constraints to make a field unique and if adding constraints is impractical then we would have to define our own method.
For example:
def self.find_first!(arg, *args)
products = where(arg, *args)
case
when products.empty?
raise_record_not_found_exception!
when products.count > 0
raise 'More than one record present'
else
products.first
end
end
Product.find_first!(price: price)
#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price)
#=> #<Product ...> (if one Product with the given price)
#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price)
Rails 7.0 has added #sole
and #find_sole_by
methods in the
ActiveRecord::FinderMethods module. These methods are used to find and assert
the presence of exactly one record.
When a user wants to find a single row, but also wants to assert that there aren't any other rows matching the condition (especially for when the database constraints aren't enough or are impractical), then these methods come into use.
For example:
class Product
validates :price, presence: true
end
Product.where(["price = %?", price]).sole
#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price)
#=> #<Product ...> (if one Product with the given price)
#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price)
Product.find_sole_by(price: price)
#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price)
#=> #<Product ...> (if one Product with the given price)
#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price)
Check out the pull request for more details.
If this blog was helpful, check out our full blog archive.