---
title: Sharing Query Logic Within ActiveRecord Models
teaser: 'Re-use collection query logic within model instances to avoid duplication
  and get the best performance.

  '
tags: rails,sql,web
author: Chris Toomey
published_on: 2023-11-22
---

I was recently working on an application in which we had a [class method / scope]
used to return a specific set of users. This was fine and good until we found
ourselves needing to capture the same logic at the instance level. Ideally, I
was hoping to be able to avoid duplicating the logic, and simultaneously keep
things efficient.

Users in our application can be configured to receive alerts. We have a class
method on the `User` model used to query for all the users who can receive
alerts:

``` rb
class User < ApplicationRecord
  def self.can_receive_alerts
    where(receives_sms_alerts: true).
      or(where(receives_email_alerts: true)).
      joins(:alert_configurations).
      distinct
  end
end
```

In words, the criteria for `can_receive_alerts` is that a user has either email
or sms alerts enabled, and at least one `AlertConfiguration` defined. This
method will result in roughly the following SQL query:

``` sql
SELECT * FROM users
  INNER JOIN alert_configurations ON alert_configurations.user_id = users.id
  WHERE (users.receives_sms_alerts = 't' OR users.receives_email_alerts = 't');
```

We'd like to be able to reuse this same query logic at the instance level, such
that we can answer `user.can_receive_alerts?` for a specific model instance,
ideally without having to duplicate the query logic. Thankfully, we can take
advantage of [ActiveRecord's lazy query evaluation] and build on the relation
returned from the class method:

``` rb
class User < ApplicationRecord
  # our class method from above
  def self.can_receive_alerts
    where(receives_sms_alerts: true).
      or(where(receives_email_alerts: true)).
      joins(:alert_configurations).
      distinct
  end

  # our new instance method which builds on the class method
  def can_receive_alerts?
    self.class.can_receive_alerts.where(id: id).exists?
  end
end
```

This new instance method, `can_receive_alerts?`, directly reuses the query logic
from the class method, scoping it down to the user instance in question by
chaining `where(id: id)`, and then using [ActiveRecord's `exists?` method] to
produce an optimally efficient SQL query:

``` sql
SELECT 1 AS one FROM users
  INNER JOIN alert_configurations ON alert_configurations.user_id = users.id
  WHERE (users.receives_sms_alerts = 't' OR users.receives_email_alerts = 't')
  AND users.id = 1
  LIMIT 1;
```

In the past, I likely would have either duplicated the logic at the instance
level (and inevitably had them fall out of sync), or possibly used Ruby
to scan the full set of users returned by the scope. Instead, we have a precise
query which will only return the absolute minimum data needed, while directly
reusing the query logic from the scope.

My focus is on ensuring that the core logic around how our application defines
if a user can receive alerts is defined in only a single, canonical spot, but the
performance niceties that come along are a wonderful side effect.

[class method / scope]: https://guides.rubyonrails.org/active_record_querying.html#scopes
[ActiveRecord's lazy query evaluation]: https://www.theodinproject.com/lessons/ruby-on-rails-active-record-queries#relations-and-lazy-evaluation
[ActiveRecord's `exists?` method]: https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F
