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
- 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).
- Keep only the hash and encode the extra logic as additional hash/SQL conditions to keep queries working.
- 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
- Pick one style per rule: hash (query-friendly), SQL string (query-only), or block (instance-only).
- Decide up front whether the subject needs accessible_by; that dictates hash vs block.
- Instantiate the Ability in a spec for each role — BlockAndConditionsError fires at definition time and is caught immediately.
- During refactors, split combined rules into two rules rather than merging hash + block.
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
- You can't specify target (#{target}) as alias because it is
- The can? and cannot? call cannot be used with a raw sql 'can
- Subject is required for #{action}
- You are not authorized to access this page.
- accessible_by_strategy = :subquery requires ActiveRecord 5 o
AI-assisted analysis of CanCanCommunity/cancancan@8c1bf153a3 (2026-08-21).
Data as JSON: /api/errors/af5d6dd3dd63601e.
Report an issue: GitHub.