CanCanCommunity/cancancan · error · CanCan::BlockAndConditionsError

A hash of conditions is mutually exclusive with a block. Che

Error message

A hash of conditions is mutually exclusive with a block. Check ":#{action} #{subject}" ability.

What it means

Rule#condition_and_block_check (lib/cancan/rule.rb:126) raises CanCan::BlockAndConditionsError when a single can/cannot call passes BOTH a conditions hash and a block. The two forms are ambiguous for query generation (hash becomes SQL, block is Ruby-only), so CanCanCan refuses to guess; the message names the offending ":action subject" ability.

Source

Thrown at lib/cancan/rule.rb:126

    def matches_subject?(subject)
      @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
    end

    def matches_subject_class?(subject)
      SubjectClassMatcher.matches_subject_class?(@subjects, subject)
    end

    def parse_attributes_from_extra_args(args)
      attributes = args.shift if valid_attribute_param?(args.first)
      extra_args = args.shift
      [attributes, extra_args]
    end

    def condition_and_block_check(conditions, block, action, subject)
      return unless conditions.is_a?(Hash) && block

      raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
        "Check \":#{action} #{subject}\" ability."
    end

    def wrap(object)
      if object.nil?
        []
      elsif object.respond_to?(:to_ary)
        object.to_ary || [object]
      else
        [object]
      end
    end
  end
end

View on GitHub (pinned to 8c1bf153a3)

Solutions

  1. Keep only the block and move hash checks inside it: can :manage, Project { |p| p.owner_id == user.id && p.active? } (note: breaks accessible_by).
  2. Keep only the hash and encode the extra logic as additional hash/SQL conditions to keep queries working.
  3. If the intent was OR, define two separate rules; if AND with complex logic, use the block form and avoid accessible_by for that subject.

Example fix

# before
can :manage, Project, owner_id: user.id { |p| p.active? }
# -> BlockAndConditionsError

# after (query-friendly)
can :manage, Project, owner_id: user.id, active: true

# or (ruby-only, do not call accessible_by on Project)
can :manage, Project { |p| p.owner_id == user.id && p.active? }
Defensive patterns

Strategy: validation

Validate before calling

# wrapper that rejects the illegal combination up front
def safe_can(action, subject, conditions = nil, &block)
  raise ArgumentError, 'conditions hash and block are mutually exclusive' if conditions.is_a?(Hash) && block
  block ? can(action, subject, &block) : can(action, subject, conditions)
end

Type guard

def hash_and_block?(conditions, block)
  conditions.is_a?(Hash) && block_given?
end

Prevention

When it happens

Trigger: can :manage, Project, owner_id: user.id { |p| p.active? }; cannot :destroy, Article, featured: true { |a| !a.draft? } — any rule with hash + block in one call. The rule fails at definition time, so the app breaks the moment the Ability class is instantiated.

Common situations: Starting with hash conditions and later tacking on a block for an extra check instead of refactoring; merging two rules during cleanup into one illegal combined call.

Related errors


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