Active Record scopes are a great way to introduce domain terms into your code without repeating queries all over the place.

What happens when you want to reuse a scope from one model in another?

Let's go through an example.

Say we have two models: Customer and Device. And a customer has_one :device.

Within Device, I've written a scope to select devices that have been shipped. It looks like this:

class Device < ActiveRecord::Base
  scope :shipped, -> {
    where.not(shipped_at: nil)
  }
end

Later on, you want to add a scope to Customer to select all of the customers with a shipped device.

Here's how I would normally write that:

class Customer < ActiveRecord::Base
  scope :with_shipped_device, -> {
    joins(:device).where.not(device: {shipped_at: nil})
  end
end

And here's the SQL this produces:

SELECT `customers`.* FROM `customers`
INNER JOIN `devices` ON `devices`.`customer_id` = `customers`.`id`
WHERE (`devices`.`shipped_at` IS NOT NULL)

A few things bug me about this code:

  1. The implementation of both scopes is almost identical
  2. I'm not getting to re-use my scope on the Device model
  3. Instead of using my domain terms, I'm pretty much writing SQL

Enter Relation#merge

It turns out, there is a way to reduce this type of duplication. You can call merge on any Active Record relation to merge in other scopes - and it works across models.

So, the scope on Customer can be rewritten like so:

class Customer < ActiveRecord::Base
  scope :with_shipped_device, -> {
    joins(:device).merge(Device.shipped)
  }
end

This produces the exact same SQL query shown above.

I like this code better because...

Check out merge to see if it helps you improve your scopes.