CanCanCommunity/cancancan · error · CanCan::Error
The accessible_by call cannot be used with a block 'can' def
Error message
The accessible_by call cannot be used with a block 'can' definition.The SQL cannot be determined for #{action.inspect} #{subject.inspect} What it means
Model.accessible_by(ability) and load_and_authorize_resource index actions must compile ability rules into SQL. A rule defined with only a block (can :read, Invoice { |i| ... }) cannot be expressed in SQL, so Rules#relevant_rules_for_query (lib/cancan/ability/rules.rb:75) raises CanCan::Error. Note that 'cannot' rules with attributes are silently rejected for queries, but a block rule is fatal.
Source
Thrown at lib/cancan/ability/rules.rb:75
end
def relevant_rules_for_match(action, subject)
relevant_rules(action, subject).each do |rule|
next unless rule.only_raw_sql?
raise Error,
"The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
"The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
end
end
def relevant_rules_for_query(action, subject)
rules = relevant_rules(action, subject).reject do |rule|
# reject 'cannot' rules with attributes when doing queries
rule.base_behavior == false && rule.attributes.present?
end
if rules.any?(&:only_block?)
raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
"The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
end
rules
end
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
def optimize_order!(rules)
first_can_in_group = -1
rules.each_with_index do |rule, i|
(first_can_in_group = -1) && next unless rule.base_behavior
(first_can_in_group = i) && next if first_can_in_group == -1
next unless rule.subjects == [:all]
rules[i] = rules[first_can_in_group]
rules[first_can_in_group] = rule
first_can_in_group += 1
end
endView on GitHub (pinned to 8c1bf153a3)
Solutions
- Convert the block to a conditions hash when the logic is attribute-based: can :manage, Invoice, amount: 0..100.
- Convert to a raw SQL string when it maps to SQL but not to a hash: can :manage, Invoice, 'amount < 100' (then never can? instances).
- For genuinely dynamic logic, skip accessible_by: load records with your own scope in the controller and filter in Ruby: @invoices = Invoice.all.select { |i| can?(:read, i) }.
- Split abilities per action so only the listing action uses query-friendly conditions.
Example fix
# before
can :read, Invoice { |i| i.amount < 100 }
# InvoicesController#index -> Invoice.accessible_by(current_ability) raises
# after
can :read, Invoice, amount: 0..100.0 # hash compiles to SQL for accessible_by
# or manual filtering for non-SQL logic
def index
@invoices = Invoice.all.select { |i| can?(:read, i) }
end Defensive patterns
Strategy: validation
Validate before calling
# before Model.accessible_by(ability)
def queryable?(ability, action, model)
ability.rules.none? { |rule| rule.only_block? && rule.relevant?(action, model) }
end
raise CanCan::Error, 'block rule blocks accessible_by' unless queryable?(current_ability, :read, Invoice) Type guard
def block_only_rule?(rule) rule.respond_to?(:only_block?) && rule.only_block? end
Try / catch
begin
@invoices = Invoice.accessible_by(current_ability)
rescue CanCan::Error
@invoices = Invoice.all.select { |i| can?(:read, i) } # manual fallback
end Prevention
- Default to hash conditions; reach for blocks only when the logic cannot be expressed as a hash or SQL.
- Request-spec every index action — it is the one action that goes through accessible_by.
- Keep a lint spec: assert ability.rules for query-loaded models contain no only_block? rules.
- Encapsulate the Ruby-filter fallback in one controller concern so it is applied consistently.
When it happens
Trigger: can :manage, Invoice { |i| i.amount < 100 } followed by Invoice.accessible_by(current_ability) or current_ability.model_adapter(Invoice, :read).database_records; any index action of load_and_authorize_resource on a model whose abilities use blocks; controller_specs hitting index with block-based abilities.
Common situations: Delegated/traversal logic in blocks (i.brand.owner_id == user.company_id) for index listings; abilities written block-first because they feel like Ruby, then the dashboard list page raises; upgrading CanCanCan 2 -> 3 where previously-tolerated mixed rules now raise.
Related errors
- You are not authorized to access this page.
- You are not authorized to access this page.
- This action failed the check_authorization because it does n
- This model adapter does not support fetching records from th
- Unable to merge an Active Record scope with other conditions
AI-assisted analysis of CanCanCommunity/cancancan@8c1bf153a3 (2026-08-21).
Data as JSON: /api/errors/5252659b1bb9eda6.
Report an issue: GitHub.