Merging two arbitrary ActiveRecord scopes

Updated . Posted . Visible to the public. Repeats.

(Rails has a method ActiveRecord::Relation#merge that can merge ActiveRecord scopes. However, its behavior has never been clear, and in Rails 7 it still discards conditions on the same column by the last condition. We discourage using #merge!)

The best way to merge ActiveRecord scopes is using a subquery:

scope_a.where(id: scope_b)

It is a little less concise than #merge, but unambiguous.

Example

Assume a model where a deal has many documents:

class Deal < ApplicationRecord
  has_many :documents
end

class Document < ApplicationRecord
  belongs_to :deal
end

You also have a Consul power that specifies which deals and documents are accessible by a user:

class Power
  include Consul::Power
  
  power :deals do
    Deal.where(author_id: user.id)
  end

  power :documents do
    admin? ? Document.all : Document.where(visibility: 'public')
  end
end

Now there should be a screen to list documents pertaining to a given deal. When we implement the controller, we must filter the document list by two conditions:

  • Only show documents pertaining to the given deal (Deal#documents)
  • Only show documents that the user is allowed to see (Power#documents)

Combine both scopes with scope_a.where(id: scope_b):

class DealDocumentsController < ApplicationController

  def index
    @deal = current_power.deals.find(params[:deal_id])
    @documents = current_power.documents.where(id: @deal.documents) # <-- Here
  end

end

You can also merge scopes for different models: old blog post, card

Henning Koch
Last edit
Jonas Schiele
Keywords
chain, rails, relation
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2011-06-08 11:50)