Rails 6.1 adds ActiveRecord methods `#sole` and `#find_sole_by`


Inspired by Django’s get() method, Rails now introduces #sole and #find_sole_by. The purpose of this query is given a set of constraints, to match only one unique record in the database.

It can be used when we need to fetch only a single row, but also want to assert that there aren’t multiple rows matching the condition; especially when database constraints aren’t enough or are impractical.

Before

For example, imagine the need to find a user’s API key entry. It is assumed that each API key is unique, but anything can happen!

We might have to do the following,

api_keys = user.api_keys.where(key: key)

if api_keys.length == 1
  api_key = api_keys.first
elsif api_keys.length == 0
  raise "No records found!"
else
  raise "More than one matching record found!"
end

Now, this is impractical for so many reasons! Like above, where we need to first peek into the length of the results to determine their uniqueness. When extrapolating this to include querying between multiple models, the complexity increases.

Enter #sole and #find_sole_by!

After

This commit adds these two query options to ActiveRecord. Let’s check out how they work!

The #find_sole_by method works similar to #find_by query, where it aims to “find” a record matching a constraint. However, with #find_sole_by it ensures that this is the unique record matched.

Our previous example can now be reduced to,

user.api_keys.find_sole_by(key: key)
# => ActiveRecord::RecordNotFound      (if no matching API Key)
# => #<APIKey ...>                     (if one API Key)
# => ActiveRecord::SoleRecordExceeded  (if more than one API Key)

The above query will return the ONLY matching record if it exists. It will raise an ActiveRecord::RecordNotFound if no record is found, or raises an ActiveRecord::SoleRecordExceeded if more than one record is found.

Let’s look at another example of #sole. This behaves like #first, but instead of just extracting the first result, it extracts the unique result matching said constraints.

APIKey.where(key: key, user_id: user.id).sole
# => ActiveRecord::RecordNotFound      (if no matching API Key)
# => #<APIKey ...>                     (if one API Key)
# => ActiveRecord::SoleRecordExceeded  (if more than one API Key)