cancancan 1.13.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/cancancan.gemspec +18 -18
- data/init.rb +2 -0
- data/lib/cancan.rb +9 -11
- data/lib/cancan/ability.rb +93 -194
- data/lib/cancan/ability/actions.rb +93 -0
- data/lib/cancan/ability/rules.rb +93 -0
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/conditions_matcher.rb +106 -0
- data/lib/cancan/controller_additions.rb +38 -41
- data/lib/cancan/controller_resource.rb +52 -211
- data/lib/cancan/controller_resource_builder.rb +26 -0
- data/lib/cancan/controller_resource_finder.rb +42 -0
- data/lib/cancan/controller_resource_loader.rb +120 -0
- data/lib/cancan/controller_resource_name_finder.rb +23 -0
- data/lib/cancan/controller_resource_sanitizer.rb +32 -0
- data/lib/cancan/exceptions.rb +17 -5
- data/lib/cancan/matchers.rb +12 -3
- data/lib/cancan/model_adapters/abstract_adapter.rb +10 -8
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +39 -13
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +68 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +77 -82
- data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
- data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_additions.rb +2 -1
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/relevant.rb +29 -0
- data/lib/cancan/rule.rb +76 -105
- data/lib/cancan/rules_compressor.rb +23 -0
- data/lib/cancan/unauthorized_message_resolver.rb +24 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +4 -2
- data/lib/generators/cancan/ability/templates/ability.rb +2 -0
- metadata +66 -56
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.travis.yml +0 -28
- data/Appraisals +0 -81
- data/CHANGELOG.rdoc +0 -518
- data/CONTRIBUTING.md +0 -23
- data/Gemfile +0 -3
- data/LICENSE +0 -22
- data/README.md +0 -214
- data/Rakefile +0 -9
- data/gemfiles/activerecord_3.2.gemfile +0 -16
- data/gemfiles/activerecord_4.0.gemfile +0 -17
- data/gemfiles/activerecord_4.1.gemfile +0 -17
- data/gemfiles/activerecord_4.2.gemfile +0 -18
- data/gemfiles/mongoid_2.x.gemfile +0 -16
- data/gemfiles/sequel_3.x.gemfile +0 -16
- data/lib/cancan/inherited_resource.rb +0 -20
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
- data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
- data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
- data/spec/README.rdoc +0 -27
- data/spec/cancan/ability_spec.rb +0 -521
- data/spec/cancan/controller_additions_spec.rb +0 -141
- data/spec/cancan/controller_resource_spec.rb +0 -632
- data/spec/cancan/exceptions_spec.rb +0 -58
- data/spec/cancan/inherited_resource_spec.rb +0 -71
- data/spec/cancan/matchers_spec.rb +0 -29
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -85
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -384
- data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
- data/spec/cancan/rule_spec.rb +0 -52
- data/spec/matchers.rb +0 -13
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -27
- data/spec/support/ability.rb +0 -7
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # this class is responsible of converting the hash of conditions
         | 
| 4 | 
            +
            # in "where conditions" to generate the sql query
         | 
| 5 | 
            +
            # it consists of a names_cache that helps calculating the next name given to the association
         | 
| 6 | 
            +
            # it tries to reflect the bahavior of ActiveRecord when generating aliases for tables.
         | 
| 7 | 
            +
            module CanCan
         | 
| 8 | 
            +
              module ModelAdapters
         | 
| 9 | 
            +
                class ConditionsExtractor
         | 
| 10 | 
            +
                  def initialize(model_class)
         | 
| 11 | 
            +
                    @names_cache = { model_class.table_name => [] }.with_indifferent_access
         | 
| 12 | 
            +
                    @root_model_class = model_class
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def tableize_conditions(conditions, model_class = @root_model_class, path_to_key = 0)
         | 
| 16 | 
            +
                    return conditions unless conditions.is_a? Hash
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    conditions.each_with_object({}) do |(key, value), result_hash|
         | 
| 19 | 
            +
                      if value.is_a? Hash
         | 
| 20 | 
            +
                        result_hash.merge!(calculate_result_hash(key, model_class, path_to_key, result_hash, value))
         | 
| 21 | 
            +
                      else
         | 
| 22 | 
            +
                        result_hash[key] = value
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                      result_hash
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def calculate_result_hash(key, model_class, path_to_key, result_hash, value)
         | 
| 31 | 
            +
                    reflection = model_class.reflect_on_association(key)
         | 
| 32 | 
            +
                    nested_resulted = calculate_nested(model_class, result_hash, key, value.dup, path_to_key)
         | 
| 33 | 
            +
                    association_class = reflection.klass.name.constantize
         | 
| 34 | 
            +
                    tableize_conditions(nested_resulted, association_class, "#{path_to_key}_#{key}")
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def calculate_nested(model_class, result_hash, relation_name, value, path_to_key)
         | 
| 38 | 
            +
                    value.each_with_object({}) do |(k, v), nested|
         | 
| 39 | 
            +
                      if v.is_a? Hash
         | 
| 40 | 
            +
                        value.delete(k)
         | 
| 41 | 
            +
                        nested[k] = v
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        table_alias = generate_table_alias(model_class, relation_name, path_to_key)
         | 
| 44 | 
            +
                        result_hash[table_alias] = value
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
                      nested
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def generate_table_alias(model_class, relation_name, path_to_key)
         | 
| 51 | 
            +
                    table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    if alredy_used?(table_alias, relation_name, path_to_key)
         | 
| 54 | 
            +
                      table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      index = 1
         | 
| 57 | 
            +
                      while alredy_used?(table_alias, relation_name, path_to_key)
         | 
| 58 | 
            +
                        table_alias = "#{table_alias}_#{index += 1}".to_sym
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                    add_to_cache(table_alias, relation_name, path_to_key)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def alredy_used?(table_alias, relation_name, path_to_key)
         | 
| 65 | 
            +
                    @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}")
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def add_to_cache(table_alias, relation_name, path_to_key)
         | 
| 69 | 
            +
                    @names_cache[table_alias] ||= []
         | 
| 70 | 
            +
                    @names_cache[table_alias] << "#{path_to_key}_#{relation_name}"
         | 
| 71 | 
            +
                    table_alias
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            # this class is responsible of normalizing the hash of conditions
         | 
| 2 | 
            +
            # by exploding has_many through associations
         | 
| 3 | 
            +
            # when a condition is defined with an has_many thorugh association this is exploded in all its parts
         | 
| 4 | 
            +
            # TODO: it could identify STI and normalize it
         | 
| 5 | 
            +
            module CanCan
         | 
| 6 | 
            +
              module ModelAdapters
         | 
| 7 | 
            +
                class ConditionsNormalizer
         | 
| 8 | 
            +
                  class << self
         | 
| 9 | 
            +
                    def normalize(model_class, rules)
         | 
| 10 | 
            +
                      rules.each { |rule| rule.conditions = normalize_conditions(model_class, rule.conditions) }
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def normalize_conditions(model_class, conditions)
         | 
| 14 | 
            +
                      return conditions unless conditions.is_a? Hash
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      conditions.each_with_object({}) do |(key, value), result_hash|
         | 
| 17 | 
            +
                        if value.is_a? Hash
         | 
| 18 | 
            +
                          result_hash.merge!(calculate_result_hash(model_class, key, value))
         | 
| 19 | 
            +
                        else
         | 
| 20 | 
            +
                          result_hash[key] = value
         | 
| 21 | 
            +
                        end
         | 
| 22 | 
            +
                        result_hash
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def calculate_result_hash(model_class, key, value)
         | 
| 29 | 
            +
                      reflection = model_class.reflect_on_association(key)
         | 
| 30 | 
            +
                      unless reflection
         | 
| 31 | 
            +
                        raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'"
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      if normalizable_association? reflection
         | 
| 35 | 
            +
                        key = reflection.options[:through]
         | 
| 36 | 
            +
                        value = { reflection.source_reflection_name => value }
         | 
| 37 | 
            +
                        reflection = model_class.reflect_on_association(key)
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      { key => normalize_conditions(reflection.klass.name.constantize, value) }
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def normalizable_association?(reflection)
         | 
| 44 | 
            +
                      reflection.options[:through].present? && !reflection.options[:source_type].present?
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module CanCan
         | 
| 4 | 
            +
              module Relevant
         | 
| 5 | 
            +
                # Matches both the action, subject, and attribute, not necessarily the conditions
         | 
| 6 | 
            +
                def relevant?(action, subject)
         | 
| 7 | 
            +
                  subject = subject.values.first if subject.class == Hash
         | 
| 8 | 
            +
                  @match_all || (matches_action?(action) && matches_subject?(subject))
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                private
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def matches_action?(action)
         | 
| 14 | 
            +
                  @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def matches_subject?(subject)
         | 
| 18 | 
            +
                  @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def matches_subject_class?(subject)
         | 
| 22 | 
            +
                  @subjects.any? do |sub|
         | 
| 23 | 
            +
                    sub.is_a?(Module) && (subject.is_a?(sub) ||
         | 
| 24 | 
            +
                        subject.class.to_s == sub.to_s ||
         | 
| 25 | 
            +
                        (subject.is_a?(Module) && subject.ancestors.include?(sub)))
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
    
        data/lib/cancan/rule.rb
    CHANGED
    
    | @@ -1,155 +1,126 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'conditions_matcher.rb'
         | 
| 4 | 
            +
            require_relative 'relevant.rb'
         | 
| 5 | 
            +
             | 
| 1 6 | 
             
            module CanCan
         | 
| 2 7 | 
             
              # This class is used internally and should only be called through Ability.
         | 
| 3 8 | 
             
              # it holds the information about a "can" call made on Ability and provides
         | 
| 4 9 | 
             
              # helpful methods to determine permission checking and conditions hash generation.
         | 
| 5 10 | 
             
              class Rule # :nodoc:
         | 
| 6 | 
            -
                 | 
| 7 | 
            -
                 | 
| 11 | 
            +
                include ConditionsMatcher
         | 
| 12 | 
            +
                include Relevant
         | 
| 13 | 
            +
                include ParameterValidators
         | 
| 14 | 
            +
                attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
         | 
| 15 | 
            +
                attr_writer :expanded_actions, :conditions
         | 
| 8 16 |  | 
| 9 17 | 
             
                # The first argument when initializing is the base_behavior which is a true/false
         | 
| 10 18 | 
             
                # value. True for "can" and false for "cannot". The next two arguments are the action
         | 
| 11 19 | 
             
                # and subject respectively (such as :read, @project). The third argument is a hash
         | 
| 12 20 | 
             
                # of conditions and the last one is the block passed to the "can" call.
         | 
| 13 | 
            -
                def initialize(base_behavior, action, subject,  | 
| 14 | 
            -
                   | 
| 21 | 
            +
                def initialize(base_behavior, action, subject, *extra_args, &block)
         | 
| 22 | 
            +
                  # for backwards compatibility, attributes are an optional parameter. Check if
         | 
| 23 | 
            +
                  # attributes were passed or are actually conditions
         | 
| 24 | 
            +
                  attributes, extra_args = parse_attributes_from_extra_args(extra_args)
         | 
| 25 | 
            +
                  condition_and_block_check(extra_args, block, action, subject)
         | 
| 15 26 | 
             
                  @match_all = action.nil? && subject.nil?
         | 
| 27 | 
            +
                  raise Error, "Subject is required for #{action}" if action && subject.nil?
         | 
| 28 | 
            +
             | 
| 16 29 | 
             
                  @base_behavior = base_behavior
         | 
| 17 | 
            -
                  @actions =  | 
| 18 | 
            -
                  @subjects =  | 
| 19 | 
            -
                  @ | 
| 30 | 
            +
                  @actions = wrap(action)
         | 
| 31 | 
            +
                  @subjects = wrap(subject)
         | 
| 32 | 
            +
                  @attributes = wrap(attributes)
         | 
| 33 | 
            +
                  @conditions = extra_args || {}
         | 
| 20 34 | 
             
                  @block = block
         | 
| 21 35 | 
             
                end
         | 
| 22 36 |  | 
| 23 | 
            -
                 | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 26 | 
            -
                  @match_all || (matches_action?(action) && matches_subject?(subject))
         | 
| 27 | 
            -
                end
         | 
| 37 | 
            +
                def inspect
         | 
| 38 | 
            +
                  repr = "#<#{self.class.name}"
         | 
| 39 | 
            +
                  repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
         | 
| 28 40 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                   | 
| 32 | 
            -
                     | 
| 33 | 
            -
                  elsif @block && !subject_class?(subject)
         | 
| 34 | 
            -
                    @block.call(subject, *extra_args)
         | 
| 35 | 
            -
                  elsif @conditions.kind_of?(Hash) && subject.class == Hash
         | 
| 36 | 
            -
                    nested_subject_matches_conditions?(subject)
         | 
| 37 | 
            -
                  elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
         | 
| 38 | 
            -
                    matches_conditions_hash?(subject)
         | 
| 39 | 
            -
                  else
         | 
| 40 | 
            -
                    # Don't stop at "cannot" definitions when there are conditions.
         | 
| 41 | 
            -
                    conditions_empty? ? true : @base_behavior
         | 
| 41 | 
            +
                  if with_scope?
         | 
| 42 | 
            +
                    repr += ", #{@conditions.where_values_hash}"
         | 
| 43 | 
            +
                  elsif [Hash, String].include?(@conditions.class)
         | 
| 44 | 
            +
                    repr += ", #{@conditions.inspect}"
         | 
| 42 45 | 
             
                  end
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                def only_block?
         | 
| 46 | 
            -
                  conditions_empty? && !@block.nil?
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def only_raw_sql?
         | 
| 50 | 
            -
                  @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
         | 
| 51 | 
            -
                end
         | 
| 52 46 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
                  @conditions == {} || @conditions.nil?
         | 
| 47 | 
            +
                  repr + '>'
         | 
| 55 48 | 
             
                end
         | 
| 56 49 |  | 
| 57 | 
            -
                def  | 
| 58 | 
            -
                   | 
| 59 | 
            -
                    (!@conditions.keys.first.kind_of? Symbol)
         | 
| 50 | 
            +
                def can_rule?
         | 
| 51 | 
            +
                  base_behavior
         | 
| 60 52 | 
             
                end
         | 
| 61 53 |  | 
| 62 | 
            -
                def  | 
| 63 | 
            -
                   | 
| 64 | 
            -
                  conditions.map do |name, value|
         | 
| 65 | 
            -
                    hash[name] = associations_hash(value) if value.kind_of? Hash
         | 
| 66 | 
            -
                  end if conditions.kind_of? Hash
         | 
| 67 | 
            -
                  hash
         | 
| 54 | 
            +
                def cannot_catch_all?
         | 
| 55 | 
            +
                  !can_rule? && catch_all?
         | 
| 68 56 | 
             
                end
         | 
| 69 57 |  | 
| 70 | 
            -
                def  | 
| 71 | 
            -
                   | 
| 72 | 
            -
             | 
| 73 | 
            -
                    attributes[key] = value unless [Array, Range, Hash].include? value.class
         | 
| 74 | 
            -
                  end if @conditions.kind_of? Hash
         | 
| 75 | 
            -
                  attributes
         | 
| 58 | 
            +
                def catch_all?
         | 
| 59 | 
            +
                  (with_scope? && @conditions.where_values_hash.empty?) ||
         | 
| 60 | 
            +
                    (!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
         | 
| 76 61 | 
             
                end
         | 
| 77 62 |  | 
| 78 | 
            -
                 | 
| 79 | 
            -
             | 
| 80 | 
            -
                def subject_class?(subject)
         | 
| 81 | 
            -
                  klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
         | 
| 82 | 
            -
                  klass == Class || klass == Module
         | 
| 63 | 
            +
                def only_block?
         | 
| 64 | 
            +
                  conditions_empty? && @block
         | 
| 83 65 | 
             
                end
         | 
| 84 66 |  | 
| 85 | 
            -
                def  | 
| 86 | 
            -
                  @ | 
| 67 | 
            +
                def only_raw_sql?
         | 
| 68 | 
            +
                  @block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
         | 
| 87 69 | 
             
                end
         | 
| 88 70 |  | 
| 89 | 
            -
                def  | 
| 90 | 
            -
                  @ | 
| 71 | 
            +
                def with_scope?
         | 
| 72 | 
            +
                  @conditions.is_a?(ActiveRecord::Relation)
         | 
| 91 73 | 
             
                end
         | 
| 92 74 |  | 
| 93 | 
            -
                def  | 
| 94 | 
            -
                   | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 75 | 
            +
                def associations_hash(conditions = @conditions)
         | 
| 76 | 
            +
                  hash = {}
         | 
| 77 | 
            +
                  if conditions.is_a? Hash
         | 
| 78 | 
            +
                    conditions.map do |name, value|
         | 
| 79 | 
            +
                      hash[name] = associations_hash(value) if value.is_a? Hash
         | 
| 80 | 
            +
                    end
         | 
| 98 81 | 
             
                  end
         | 
| 82 | 
            +
                  hash
         | 
| 99 83 | 
             
                end
         | 
| 100 84 |  | 
| 101 | 
            -
                 | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                  return true if conditions.empty?
         | 
| 107 | 
            -
                  adapter = model_adapter(subject)
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                  if adapter.override_conditions_hash_matching?(subject, conditions)
         | 
| 110 | 
            -
                    return adapter.matches_conditions_hash?(subject, conditions)
         | 
| 111 | 
            -
                  end
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                  conditions.all? do |name, value|
         | 
| 114 | 
            -
                    if adapter.override_condition_matching?(subject, name, value)
         | 
| 115 | 
            -
                      return adapter.matches_condition?(subject, name, value)
         | 
| 85 | 
            +
                def attributes_from_conditions
         | 
| 86 | 
            +
                  attributes = {}
         | 
| 87 | 
            +
                  if @conditions.is_a? Hash
         | 
| 88 | 
            +
                    @conditions.each do |key, value|
         | 
| 89 | 
            +
                      attributes[key] = value unless [Array, Range, Hash].include? value.class
         | 
| 116 90 | 
             
                    end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                    condition_match?(subject.send(name), value)
         | 
| 119 91 | 
             
                  end
         | 
| 92 | 
            +
                  attributes
         | 
| 120 93 | 
             
                end
         | 
| 121 94 |  | 
| 122 | 
            -
                def  | 
| 123 | 
            -
                   | 
| 124 | 
            -
                   | 
| 125 | 
            -
                end
         | 
| 95 | 
            +
                def matches_attributes?(attribute)
         | 
| 96 | 
            +
                  return true if @attributes.empty?
         | 
| 97 | 
            +
                  return @base_behavior if attribute.nil?
         | 
| 126 98 |  | 
| 127 | 
            -
             | 
| 128 | 
            -
                  if subject.class == Class
         | 
| 129 | 
            -
                    @block.call(action, subject, nil, *extra_args)
         | 
| 130 | 
            -
                  else
         | 
| 131 | 
            -
                    @block.call(action, subject.class, subject, *extra_args)
         | 
| 132 | 
            -
                  end
         | 
| 99 | 
            +
                  @attributes.include?(attribute.to_sym)
         | 
| 133 100 | 
             
                end
         | 
| 134 101 |  | 
| 135 | 
            -
                 | 
| 136 | 
            -
             | 
| 102 | 
            +
                private
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def parse_attributes_from_extra_args(args)
         | 
| 105 | 
            +
                  attributes = args.shift if valid_attribute_param?(args.first)
         | 
| 106 | 
            +
                  extra_args = args.shift
         | 
| 107 | 
            +
                  [attributes, extra_args]
         | 
| 137 108 | 
             
                end
         | 
| 138 109 |  | 
| 139 | 
            -
                def  | 
| 140 | 
            -
                   | 
| 141 | 
            -
             | 
| 142 | 
            -
                   | 
| 143 | 
            -
             | 
| 144 | 
            -
                  else attribute == value
         | 
| 145 | 
            -
                  end
         | 
| 110 | 
            +
                def condition_and_block_check(conditions, block, action, subject)
         | 
| 111 | 
            +
                  return unless conditions.is_a?(Hash) && block
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\
         | 
| 114 | 
            +
                    "Check \":#{action} #{subject}\" ability."
         | 
| 146 115 | 
             
                end
         | 
| 147 116 |  | 
| 148 | 
            -
                def  | 
| 149 | 
            -
                  if  | 
| 150 | 
            -
                     | 
| 117 | 
            +
                def wrap(object)
         | 
| 118 | 
            +
                  if object.nil?
         | 
| 119 | 
            +
                    []
         | 
| 120 | 
            +
                  elsif object.respond_to?(:to_ary)
         | 
| 121 | 
            +
                    object.to_ary || [object]
         | 
| 151 122 | 
             
                  else
         | 
| 152 | 
            -
                     | 
| 123 | 
            +
                    [object]
         | 
| 153 124 | 
             
                  end
         | 
| 154 125 | 
             
                end
         | 
| 155 126 | 
             
              end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'conditions_matcher.rb'
         | 
| 4 | 
            +
            module CanCan
         | 
| 5 | 
            +
              class RulesCompressor
         | 
| 6 | 
            +
                attr_reader :initial_rules, :rules_collapsed
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(rules)
         | 
| 9 | 
            +
                  @initial_rules = rules
         | 
| 10 | 
            +
                  @rules_collapsed = compress(@initial_rules)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def compress(array)
         | 
| 14 | 
            +
                  idx = array.rindex(&:catch_all?)
         | 
| 15 | 
            +
                  return array unless idx
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  value = array[idx]
         | 
| 18 | 
            +
                  array[idx..-1]
         | 
| 19 | 
            +
                    .drop_while { |n| n.base_behavior == value.base_behavior }
         | 
| 20 | 
            +
                    .tap { |a| a.unshift(value) unless value.cannot_catch_all? }
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module CanCan
         | 
| 4 | 
            +
              module UnauthorizedMessageResolver
         | 
| 5 | 
            +
                def unauthorized_message(action, subject)
         | 
| 6 | 
            +
                  subject = subject.values.last if subject.is_a?(Hash)
         | 
| 7 | 
            +
                  keys = unauthorized_message_keys(action, subject)
         | 
| 8 | 
            +
                  variables = {}
         | 
| 9 | 
            +
                  variables[:action] = I18n.translate("actions.#{action}", default: action.to_s)
         | 
| 10 | 
            +
                  variables[:subject] = translate_subject(subject)
         | 
| 11 | 
            +
                  message = I18n.translate(keys.shift, **variables.merge(scope: :unauthorized, default: keys + ['']))
         | 
| 12 | 
            +
                  message.blank? ? nil : message
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def translate_subject(subject)
         | 
| 16 | 
            +
                  klass = (subject.class == Class ? subject : subject.class)
         | 
| 17 | 
            +
                  if klass.respond_to?(:model_name)
         | 
| 18 | 
            +
                    klass.model_name.human
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    klass.to_s.underscore.humanize.downcase
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         |