Rails 7.0 Added Sole Finder Methods
May 23, 2021
Rails has several finder methods like
first, etc. in
ActiveRecord to query the database to find a single record.
Let's understand how we use
first methods with the help of an example.
If we are asked to update a product
name whose price is
$1000 and we assume there could be one such product in our
We can update the product
name using a
find_by as below :
Product.find_by(price: 1000).update(name: name + ' Pro') #or Product.where(price: 1000).first.update(name: name + ' Pro')
The above query will execute and update a record in database.
But the above query did not ensure that there was a single product with price
Suppose that table had a duplicate entry as follows :
Product.create(name: 'iPhone11 Pro', price: 1000) Product.create(name: 'iPhone12', price: 1000)
Now, this is a genuine problem which can be solved as below :
class MultipleItemsFoundError < StandardError end products = Product.where(price: 1000) if products.size == 1 products.first.update(name: name + ' Pro') else raise MultipleItemsFoundError end
In the above example, we are raising an error
MultipleItemsFoundError if the database has multiple records with price
This is absolutely fine because we assumed there could be a single product with price
and if there is an exception to this then we should raise an error.
Rails 7.0 has added two new methods
ActiveRecord with this pull request
which will help us to address the above problem in much simpler way.
We can rewrite the above code as follows :
Product.find_sole_by(price: 1000).update(name: name + ' Pro') #or Product.where(price: 1000).sole.update(name: name + ' Pro') #=> ActiveRecord::SoleRecordExceeded (Wanted only one Product)
The above code raises an error
ActiveRecord::SoleRecordExceeded (Wanted only one Product)
which ensures us that
products table has more than one record with price
This query would have worked if we had only one product with price
ActiveSupport added Enumerable#sole
Rails has been adding useful methods to
ActiveSupport to ease developers job.
Let's try this out with an example :
mobile_devices = ['iPhone 11', 'iPhone12'] mobile_devices.sole #=> Enumerable::SoleItemExpectedError (multiple items found) mobile_devices = ['iPhone12'] mobile_devices.sole #=> "iPhone12"
As we have seen
find_sole_by in action,
Enumerable#sole is no different.
It ensures us that there is only one item in the
mobile_devices array and returns the single value in it.