From 10eba819c990f2644fd9391bf73abfdb46966b63 Mon Sep 17 00:00:00 2001 From: Diego Algorta Date: Sun, 22 Jan 2023 21:43:01 -0300 Subject: [PATCH] Leverage accessible_by from can? when possible --- lib/cancan/ability.rb | 13 +++++++++++++ lib/cancan/ability/rules.rb | 12 ++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index 679ffc90..e8f4c79b 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -73,6 +73,12 @@ module Ability # Also see the RSpec Matchers to aid in testing. def can?(action, subject, attribute = nil, *extra_args) match = extract_subjects(subject).lazy.map do |a_subject| + rules_for_query = rules_except_cannot_with_attributes(action, a_subject) + subject_class = subject_class?(a_subject) ? a_subject : a_subject.class + if can_use_accessible_by?(subject_class, a_subject, rules_for_query) + next subject_class.accessible_by(self, action).exists?(a_subject.send(subject_class.primary_key)) ? rules_for_query.first : nil + end + relevant_rules_for_match(action, a_subject).detect do |rule| rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute) end @@ -80,6 +86,13 @@ def can?(action, subject, attribute = nil, *extra_args) match ? match.base_behavior : false end + def can_use_accessible_by?(subject_class, subject, rules) + subject_class.respond_to?(:accessible_by) && + defined?(ActiveRecord::Base) && subject.is_a?(ActiveRecord::Base) && + !subject.new_record? && + rules.none?(&:only_block?) + end + # Convenience method which works the same as "can?" but returns the opposite value. # # cannot? :destroy, @project diff --git a/lib/cancan/ability/rules.rb b/lib/cancan/ability/rules.rb index 8bee574c..3858686a 100644 --- a/lib/cancan/ability/rules.rb +++ b/lib/cancan/ability/rules.rb @@ -67,10 +67,7 @@ def relevant_rules_for_match(action, subject) 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 + rules = rules_except_cannot_with_attributes(action, subject) 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}" @@ -78,6 +75,13 @@ def relevant_rules_for_query(action, subject) rules end + def rules_except_cannot_with_attributes(action, subject) + relevant_rules(action, subject).reject do |rule| + # reject 'cannot' rules with attributes when doing queries + rule.base_behavior == false && rule.attributes.present? + end + 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