Rails 7 adds `Enumerable#sole` to return a sole item in the enumerable


Rails 6.1 added two query options, namely #sole and #find_sole_by to the ActiveRecord::FinderMethods. These methods assert that only one unique record is returned from the database.

In Rails 7, Enumerable#sole was added to the Enumerable module which behaves similar to ActiveRecord::FinderMethods methods - #sole and #find_sole_by.

Before

Let’s say we have an app where User can have multiple api_keys associated with their accounts. Each api_key will be associated with a particular type. To implement the above scenario we will design our User and ApiKey models as shown below:


# app/models/user.rb
class User < ApplicationRecord
  has_many :api_keys
end


# app/models/api_key.rb
class ApiKey < ApplicationRecord
  belongs_to :user

  scope :github, -> { where(key_type: "github") }

  # == Schema Information
  #
  # Table name: api_keys
  #
  # key                       :string(255)
  # user_id                   :integer
  # key_type                  :string(255)
end

To verify if a user has only one API key of a particular type, we might implement the following code:


user = User.find(1)
keys = user.api_keys.github.pluck(:key)

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

As seen, we have to write a lot of code to fetch a user API key of a particular type.

After

In Rails 7, with Enumerable#sole we can replace the above code as shown below:


key = user.api_keys.github.pluck(:key).sole
=> "github key"

The Enumerable#sole method returns one and only one element and raises an error if there are none or more than one element present in the Enumerable.


key = user.api_keys.github.pluck(:key).sole

# if no API key of particular type exists
=> Enumerable::SoleItemExpectedError (no item found)

# if multiple API keys of a particular type exist
=> Enumerable::SoleItemExpectedError (multiple items found)