(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