This blog is part of our Rails 7 series.
Before Rails 7.0
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.
1Product.find_by!(price: price) 2#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price) 3#=> #<Product ...> (first product with the given price) 4 5Product.where(price: price).first! 6#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price) 7#=> #<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:
1def self.find_first!(arg, *args) 2 products = where(arg, *args) 3 4 case 5 when products.empty? 6 raise_record_not_found_exception! 7 when products.count > 0 8 raise 'More than one record present' 9 else 10 products.first 11 end 12end 13 14Product.find_first!(price: price) 15#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price) 16#=> #<Product ...> (if one Product with the given price) 17#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price)
After Rails 7.0
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:
1class Product 2 validates :price, presence: true 3end 4 5Product.where(["price = %?", price]).sole 6#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price) 7#=> #<Product ...> (if one Product with the given price) 8#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price) 9 10Product.find_sole_by(price: price) 11#=> ActiveRecord::RecordNotFound Exception (if no Product with the given price) 12#=> #<Product ...> (if one Product with the given price) 13#=> ActiveRecord::SoleRecordExceeded Exception (if more than one Product with the given price)
Check out the pull request for more details.