{"record":{"id":"5252659b1bb9eda6","repo":"CanCanCommunity/cancancan","slug":"the-accessible-by-call-cannot-be-used-with-a-block","errorCode":null,"errorMessage":"The accessible_by call cannot be used with a block 'can' definition.The SQL cannot be determined for #{action.inspect} #{subject.inspect}","messagePattern":"The accessible_by call cannot be used with a block 'can' definition\\.The SQL cannot be determined for #(.+?) #(.+?)","errorType":"exception","errorClass":"CanCan::Error","httpStatus":null,"severity":"error","filePath":"lib/cancan/ability/rules.rb","lineNumber":75,"sourceCode":"      end\n\n      def relevant_rules_for_match(action, subject)\n        relevant_rules(action, subject).each do |rule|\n          next unless rule.only_raw_sql?\n\n          raise Error,\n                \"The can? and cannot? call cannot be used with a raw sql 'can' definition. \" \\\n                \"The checking code cannot be determined for #{action.inspect} #{subject.inspect}\"\n        end\n      end\n\n      def relevant_rules_for_query(action, subject)\n        rules = relevant_rules(action, subject).reject do |rule|\n          # reject 'cannot' rules with attributes when doing queries\n          rule.base_behavior == false && rule.attributes.present?\n        end\n        if rules.any?(&:only_block?)\n          raise Error, \"The accessible_by call cannot be used with a block 'can' definition.\" \\\n            \"The SQL cannot be determined for #{action.inspect} #{subject.inspect}\"\n        end\n        rules\n      end\n\n      # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.\n      def optimize_order!(rules)\n        first_can_in_group = -1\n        rules.each_with_index do |rule, i|\n          (first_can_in_group = -1) && next unless rule.base_behavior\n          (first_can_in_group = i) && next if first_can_in_group == -1\n          next unless rule.subjects == [:all]\n\n          rules[i] = rules[first_can_in_group]\n          rules[first_can_in_group] = rule\n          first_can_in_group += 1\n        end\n      end","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/CanCanCommunity/cancancan/blob/8c1bf153a3da7b2261d6fa4a5f84eb28e2feb828/lib/cancan/ability/rules.rb#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\ncan :read, Invoice { |i| i.amount < 100 }\n# InvoicesController#index -> Invoice.accessible_by(current_ability) raises\n\n# after\ncan :read, Invoice, amount: 0..100.0   # hash compiles to SQL for accessible_by\n\n# or manual filtering for non-SQL logic\ndef index\n  @invoices = Invoice.all.select { |i| can?(:read, i) }\nend","handlingStrategy":"validation","validationCode":"# before Model.accessible_by(ability)\ndef queryable?(ability, action, model)\n  ability.rules.none? { |rule| rule.only_block? && rule.relevant?(action, model) }\nend\n\nraise CanCan::Error, 'block rule blocks accessible_by' unless queryable?(current_ability, :read, Invoice)","typeGuard":"def block_only_rule?(rule)\n  rule.respond_to?(:only_block?) && rule.only_block?\nend","tryCatchPattern":"begin\n  @invoices = Invoice.accessible_by(current_ability)\nrescue CanCan::Error\n  @invoices = Invoice.all.select { |i| can?(:read, i) } # manual fallback\nend","preventionTips":["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."],"tags":["accessible-by","block-conditions","sql","cancancan","rails"],"backgroundTag":"block-conditions-unsupported-in-query","analyzedSha":"8c1bf153a3da7b2261d6fa4a5f84eb28e2feb828","analyzedAt":"2026-08-21T20:05:55.000Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}