CanCanCommunity/cancancan · error · CanCan::Error

Unable to merge an Active Record scope with other conditions

Error message

Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability.

What it means

The ActiveRecord adapter can honor at most ONE rule whose conditions are an ActiveRecord::Relation (a scope), and only when no other condition-carrying rules apply, because two relations or a relation plus a hash cannot be safely merged into one query. override_scope / raise_override_scope_error (lib/cancan/model_adapters/active_record_adapter.rb:184) raises CanCan::Error naming the first conflicting ability when accessible_by sees multiple relation conditions.

Source

Thrown at lib/cancan/model_adapters/active_record_adapter.rb:184

          if base_hash[key].is_a?(Hash)
            deep_merge(base_hash[key], value) unless value.empty?
          else
            base_hash[key] = value
          end
        end
      end

      def override_scope
        conditions = @compressed_rules.map(&:conditions).compact
        return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
        return conditions.first if conditions.size == 1

        raise_override_scope_error
      end

      def raise_override_scope_error
        rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
        raise Error,
              'Unable to merge an Active Record scope with other conditions. ' \
              "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
      end

      def merge_conditions(sql, conditions_hash, behavior)
        if conditions_hash.blank?
          behavior ? true_sql : false_sql
        else
          merge_non_empty_conditions(behavior, conditions_hash, sql)
        end
      end

      def merge_non_empty_conditions(behavior, conditions_hash, sql)
        conditions = sanitize_sql(conditions_hash)
        case sql
        when true_sql
          behavior ? true_sql : "not (#{conditions})"
        when false_sql

View on GitHub (pinned to 8c1bf153a3)

Solutions

  1. Collapse the logic into one scope: can :read, Article, Article.where('active = ? OR featured = ?', true, true), or a custom class method scope combining the cases.
  2. Replace relation scopes with hash conditions wherever possible — hashes merge via OR in the adapter.
  3. Use a raw SQL string condition instead of a Relation (SQL strings merge like hashes).
  4. For index actions with irreconcilable scopes, fetch records manually per scope and combine in Ruby.

Example fix

# before
can :read, Article, Article.where(active: true)
can :read, Article, featured: true
# Article.accessible_by(ability) -> "Unable to merge an Active Record scope..."

# after
can :read, Article, Article.where('active = ? OR featured = ?', true, true)
# or pure hash:
can :read, Article, active: true
can :read, Article, featured: true
Defensive patterns

Strategy: validation

Validate before calling

# before accessible_by: at most one Relation-conditioned rule, and nothing else with conditions
rules = current_ability.rules.select { |r| r.relevant?(:read, Article) && r.conditions.present? }
mergeable = rules.none? { |r| r.conditions.is_a?(ActiveRecord::Relation) } || (rules.size == 1 && rules.first.conditions.is_a?(ActiveRecord::Relation))
raise CanCan::Error, 'scope conditions unmergeable' unless mergeable

Type guard

def relation_condition?(rule)
  rule.conditions.is_a?(ActiveRecord::Relation)
end

Try / catch

begin
  @articles = Article.accessible_by(current_ability)
rescue CanCan::Error => e
  Rails.logger.warn("#{e.message}; falling back to union in Ruby")
  @articles = Article.where(id: scope_a.ids | scope_b.ids)
end

Prevention

When it happens

Trigger: can :read, Article, Article.where(active: true) alongside can :read, Article, featured: true, then Article.accessible_by(current_ability); two scope rules for the same model (can :read, Post, Post.published and can :read, Post, Post.archived) both applying to the user; a single scope rule plus a hash rule on another role that merges into the same ability.

Common situations: Reusing existing model scopes as ability conditions (feels DRY) and later adding a second scoped role; multi-role users where each role contributes its own scope; index actions suddenly failing after a new permission is added.

Related errors


AI-assisted analysis of CanCanCommunity/cancancan@8c1bf153a3 (2026-08-21). Data as JSON: /api/errors/57c72eeb661fed22. Report an issue: GitHub.