cancancan 1.10.0 → 3.5.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.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +19 -21
  3. data/init.rb +2 -0
  4. data/lib/cancan/ability/actions.rb +93 -0
  5. data/lib/cancan/ability/rules.rb +96 -0
  6. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/lib/cancan/ability.rb +114 -146
  8. data/lib/cancan/class_matcher.rb +30 -0
  9. data/lib/cancan/conditions_matcher.rb +147 -0
  10. data/lib/cancan/config.rb +101 -0
  11. data/lib/cancan/controller_additions.rb +38 -41
  12. data/lib/cancan/controller_resource.rb +59 -215
  13. data/lib/cancan/controller_resource_builder.rb +26 -0
  14. data/lib/cancan/controller_resource_finder.rb +42 -0
  15. data/lib/cancan/controller_resource_loader.rb +120 -0
  16. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  17. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  18. data/lib/cancan/exceptions.rb +25 -5
  19. data/lib/cancan/matchers.rb +17 -3
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +30 -9
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +43 -15
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +157 -82
  24. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  25. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  26. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  27. data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  28. data/lib/cancan/model_adapters/strategies/base.rb +40 -0
  29. data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  30. data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  31. data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  32. data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  33. data/lib/cancan/model_additions.rb +6 -3
  34. data/lib/cancan/parameter_validators.rb +9 -0
  35. data/lib/cancan/relevant.rb +29 -0
  36. data/lib/cancan/rule.rb +79 -91
  37. data/lib/cancan/rules_compressor.rb +23 -0
  38. data/lib/cancan/sti_detector.rb +12 -0
  39. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  40. data/lib/cancan/version.rb +3 -1
  41. data/lib/cancan.rb +16 -12
  42. data/lib/cancancan.rb +2 -0
  43. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  44. data/lib/generators/cancan/ability/templates/ability.rb +9 -9
  45. metadata +82 -93
  46. data/.gitignore +0 -15
  47. data/.rspec +0 -1
  48. data/.travis.yml +0 -48
  49. data/Appraisals +0 -135
  50. data/CHANGELOG.rdoc +0 -495
  51. data/CONTRIBUTING.md +0 -23
  52. data/Gemfile +0 -3
  53. data/LICENSE +0 -22
  54. data/README.md +0 -197
  55. data/Rakefile +0 -9
  56. data/gemfiles/activerecord_3.0.gemfile +0 -18
  57. data/gemfiles/activerecord_3.1.gemfile +0 -20
  58. data/gemfiles/activerecord_3.2.gemfile +0 -20
  59. data/gemfiles/activerecord_4.0.gemfile +0 -17
  60. data/gemfiles/activerecord_4.1.gemfile +0 -17
  61. data/gemfiles/activerecord_4.2.gemfile +0 -17
  62. data/gemfiles/datamapper_1.x.gemfile +0 -14
  63. data/gemfiles/mongoid_2.x.gemfile +0 -20
  64. data/gemfiles/sequel_3.x.gemfile +0 -20
  65. data/lib/cancan/inherited_resource.rb +0 -20
  66. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -47
  67. data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
  68. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  69. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  70. data/spec/README.rdoc +0 -27
  71. data/spec/cancan/ability_spec.rb +0 -487
  72. data/spec/cancan/controller_additions_spec.rb +0 -141
  73. data/spec/cancan/controller_resource_spec.rb +0 -648
  74. data/spec/cancan/exceptions_spec.rb +0 -58
  75. data/spec/cancan/inherited_resource_spec.rb +0 -71
  76. data/spec/cancan/matchers_spec.rb +0 -29
  77. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -40
  78. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -446
  79. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
  80. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  81. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  82. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  83. data/spec/cancan/rule_spec.rb +0 -52
  84. data/spec/matchers.rb +0 -13
  85. data/spec/spec.opts +0 -2
  86. data/spec/spec_helper.rb +0 -27
  87. data/spec/support/ability.rb +0 -7
@@ -0,0 +1,47 @@
1
+ require_relative '../sti_detector'
2
+
3
+ # this class is responsible for detecting sti classes and creating new rules for the
4
+ # relevant subclasses, using the inheritance_column as a merger
5
+ module CanCan
6
+ module ModelAdapters
7
+ class StiNormalizer
8
+ class << self
9
+ def normalize(rules)
10
+ rules_cache = []
11
+ return unless defined?(ActiveRecord::Base)
12
+
13
+ rules.delete_if do |rule|
14
+ subjects = rule.subjects.select do |subject|
15
+ update_rule(subject, rule, rules_cache)
16
+ end
17
+ subjects.length == rule.subjects.length
18
+ end
19
+ rules_cache.each { |rule| rules.push(rule) }
20
+ end
21
+
22
+ private
23
+
24
+ def update_rule(subject, rule, rules_cache)
25
+ return false unless StiDetector.sti_class?(subject)
26
+
27
+ rules_cache.push(build_rule_for_subclass(rule, subject))
28
+ true
29
+ end
30
+
31
+ # create a new rule for the subclasses that links on the inheritance_column
32
+ def build_rule_for_subclass(rule, subject)
33
+ sti_conditions = { subject.inheritance_column => subject.sti_name }
34
+ new_rule_conditions =
35
+ if rule.with_scope?
36
+ rule.conditions.where(sti_conditions)
37
+ else
38
+ rule.conditions.merge(sti_conditions)
39
+ end
40
+
41
+ CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass,
42
+ new_rule_conditions, rule.block)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Base
5
+ attr_reader :adapter, :relation, :where_conditions
6
+
7
+ delegate(
8
+ :compressed_rules,
9
+ :extract_multiple_conditions,
10
+ :joins,
11
+ :model_class,
12
+ :quoted_primary_key,
13
+ :quoted_aliased_table_name,
14
+ :quoted_table_name,
15
+ to: :adapter
16
+ )
17
+ delegate :connection, :quoted_primary_key, to: :model_class
18
+ delegate :quote_table_name, to: :connection
19
+
20
+ def initialize(adapter:, relation:, where_conditions:)
21
+ @adapter = adapter
22
+ @relation = relation
23
+ @where_conditions = where_conditions
24
+ end
25
+
26
+ def aliased_table_name
27
+ @aliased_table_name ||= "#{model_class.table_name}_alias"
28
+ end
29
+
30
+ def quoted_aliased_table_name
31
+ @quoted_aliased_table_name ||= quote_table_name(aliased_table_name)
32
+ end
33
+
34
+ def quoted_table_name
35
+ @quoted_table_name ||= quote_table_name(model_class.table_name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: false
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasEachRuleAsExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where(double_exists_sql)
14
+ end
15
+
16
+ def double_exists_sql
17
+ double_exists_sql = ''
18
+
19
+ compressed_rules.each_with_index do |rule, index|
20
+ double_exists_sql << ' OR ' if index.positive?
21
+ double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})"
22
+ end
23
+
24
+ double_exists_sql
25
+ end
26
+
27
+ def sub_query_for_rule(rule)
28
+ conditions_extractor = ConditionsExtractor.new(model_class)
29
+ rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule])
30
+ joins_hash, left_joins_hash = extract_joins_from_rule(rule)
31
+ sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
32
+ end
33
+
34
+ def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
35
+ model_class
36
+ .select('1')
37
+ .joins(joins_hash)
38
+ .left_joins(left_joins_hash)
39
+ .where(
40
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
41
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
42
+ )
43
+ .where(rule_where_conditions)
44
+ .limit(1)
45
+ end
46
+
47
+ def extract_joins_from_rule(rule)
48
+ joins = {}
49
+ left_joins = {}
50
+
51
+ extra_joins_recursive([], rule.conditions, joins, left_joins)
52
+ [joins, left_joins]
53
+ end
54
+
55
+ def extra_joins_recursive(current_path, conditions, joins, left_joins)
56
+ conditions.each do |key, value|
57
+ if value.is_a?(Hash)
58
+ current_path << key
59
+ extra_joins_recursive(current_path, value, joins, left_joins)
60
+ current_path.pop
61
+ else
62
+ extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
63
+ end
64
+ end
65
+ end
66
+
67
+ def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
68
+ hash_joins = current_path_to_hash(current_path)
69
+
70
+ if value.nil?
71
+ left_joins.deep_merge!(hash_joins)
72
+ else
73
+ joins.deep_merge!(hash_joins)
74
+ end
75
+ end
76
+
77
+ # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}}
78
+ def current_path_to_hash(current_path)
79
+ hash_joins = {}
80
+ current_hash_joins = hash_joins
81
+
82
+ current_path.each do |path_part|
83
+ new_hash = {}
84
+ current_hash_joins[path_part] = new_hash
85
+ current_hash_joins = new_hash
86
+ end
87
+
88
+ hash_joins
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})")
14
+ end
15
+
16
+ def joined_alias_exists_subquery_inner_query
17
+ model_class
18
+ .unscoped
19
+ .select('1')
20
+ .left_joins(joins)
21
+ .where(*where_conditions)
22
+ .where(
23
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
24
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
25
+ )
26
+ .limit(1)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class LeftJoin < Base
5
+ def execute!
6
+ relation.left_joins(joins).distinct
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Subquery < Base
5
+ def execute!
6
+ build_joins_relation_subquery(where_conditions)
7
+ end
8
+
9
+ def build_joins_relation_subquery(where_conditions)
10
+ inner = model_class.unscoped do
11
+ model_class.left_joins(joins).where(*where_conditions)
12
+ end
13
+ model_class.where(model_class.primary_key => inner)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,6 @@
1
- module CanCan
1
+ # frozen_string_literal: true
2
2
 
3
+ module CanCan
3
4
  # This module adds the accessible_by class method to a model. It is included in the model adapters.
4
5
  module ModelAdditions
5
6
  module ClassMethods
@@ -19,8 +20,10 @@ module CanCan
19
20
  # @articles = Article.accessible_by(current_ability, :update)
20
21
  #
21
22
  # Here only the articles which the user can update are returned.
22
- def accessible_by(ability, action = :index)
23
- ability.model_adapter(self, action).database_records
23
+ def accessible_by(ability, action = :index, strategy: CanCan.accessible_by_strategy)
24
+ CanCan.with_accessible_by_strategy(strategy) do
25
+ ability.model_adapter(self, action).database_records
26
+ end
24
27
  end
25
28
  end
26
29
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ParameterValidators
5
+ def valid_attribute_param?(attribute)
6
+ attribute.nil? || attribute.is_a?(Symbol) || (attribute.is_a?(Array) && attribute.all? { |a| a.is_a?(Symbol) })
7
+ end
8
+ end
9
+ 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,87 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'conditions_matcher.rb'
4
+ require_relative 'class_matcher.rb'
5
+ require_relative 'relevant.rb'
6
+
1
7
  module CanCan
2
8
  # This class is used internally and should only be called through Ability.
3
9
  # it holds the information about a "can" call made on Ability and provides
4
10
  # helpful methods to determine permission checking and conditions hash generation.
5
11
  class Rule # :nodoc:
6
- attr_reader :base_behavior, :subjects, :actions, :conditions
7
- attr_writer :expanded_actions
12
+ include ConditionsMatcher
13
+ include Relevant
14
+ include ParameterValidators
15
+ attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
16
+ attr_writer :expanded_actions, :conditions
8
17
 
9
18
  # The first argument when initializing is the base_behavior which is a true/false
10
19
  # value. True for "can" and false for "cannot". The next two arguments are the action
11
20
  # and subject respectively (such as :read, @project). The third argument is a hash
12
21
  # of conditions and the last one is the block passed to the "can" call.
13
- def initialize(base_behavior, action, subject, conditions, block)
14
- raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
22
+ def initialize(base_behavior, action, subject, *extra_args, &block)
23
+ # for backwards compatibility, attributes are an optional parameter. Check if
24
+ # attributes were passed or are actually conditions
25
+ attributes, extra_args = parse_attributes_from_extra_args(extra_args)
26
+ condition_and_block_check(extra_args, block, action, subject)
15
27
  @match_all = action.nil? && subject.nil?
28
+ raise Error, "Subject is required for #{action}" if action && subject.nil?
29
+
16
30
  @base_behavior = base_behavior
17
- @actions = [action].flatten
18
- @subjects = [subject].flatten
19
- @conditions = conditions || {}
31
+ @actions = wrap(action)
32
+ @subjects = wrap(subject)
33
+ @attributes = wrap(attributes)
34
+ @conditions = extra_args || {}
20
35
  @block = block
21
36
  end
22
37
 
23
- # Matches both the subject and action, not necessarily the conditions
24
- def relevant?(action, subject)
25
- subject = subject.values.first if subject.class == Hash
26
- @match_all || (matches_action?(action) && matches_subject?(subject))
27
- end
38
+ def inspect
39
+ repr = "#<#{self.class.name}"
40
+ repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
28
41
 
29
- # Matches the block or conditions hash
30
- def matches_conditions?(action, subject, extra_args)
31
- if @match_all
32
- call_block_with_all(action, subject, extra_args)
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
42
+ if with_scope?
43
+ repr += ", #{@conditions.where_values_hash}"
44
+ elsif [Hash, String].include?(@conditions.class)
45
+ repr += ", #{@conditions.inspect}"
42
46
  end
47
+
48
+ repr + '>'
43
49
  end
44
50
 
45
- def only_block?
46
- conditions_empty? && !@block.nil?
51
+ def can_rule?
52
+ base_behavior
47
53
  end
48
54
 
49
- def only_raw_sql?
50
- @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
55
+ def cannot_catch_all?
56
+ !can_rule? && catch_all?
57
+ end
58
+
59
+ def catch_all?
60
+ (with_scope? && @conditions.where_values_hash.empty?) ||
61
+ (!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
51
62
  end
52
63
 
53
- def conditions_empty?
54
- @conditions == {} || @conditions.nil?
64
+ def only_block?
65
+ conditions_empty? && @block
66
+ end
67
+
68
+ def only_raw_sql?
69
+ @block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
55
70
  end
56
71
 
57
- def unmergeable?
58
- @conditions.respond_to?(:keys) && @conditions.present? &&
59
- (!@conditions.keys.first.kind_of? Symbol)
72
+ def with_scope?
73
+ defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
60
74
  end
61
75
 
62
76
  def associations_hash(conditions = @conditions)
63
77
  hash = {}
64
- conditions.map do |name, value|
65
- hash[name] = associations_hash(value) if value.kind_of? Hash
66
- end if conditions.kind_of? Hash
78
+ if conditions.is_a? Hash
79
+ conditions.map do |name, value|
80
+ hash[name] = associations_hash(value) if value.is_a? Hash
81
+ end
82
+ end
67
83
  hash
68
84
  end
69
85
 
70
86
  def attributes_from_conditions
71
87
  attributes = {}
72
- @conditions.each do |key, value|
73
- attributes[key] = value unless [Array, Range, Hash].include? value.class
74
- end if @conditions.kind_of? Hash
88
+ if @conditions.is_a? Hash
89
+ @conditions.each do |key, value|
90
+ attributes[key] = value unless [Array, Range, Hash].include? value.class
91
+ end
92
+ end
75
93
  attributes
76
94
  end
77
95
 
78
- private
96
+ def matches_attributes?(attribute)
97
+ return true if @attributes.empty?
98
+ return @base_behavior if attribute.nil?
79
99
 
80
- def subject_class?(subject)
81
- klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
82
- klass == Class || klass == Module
100
+ @attributes.include?(attribute.to_sym)
83
101
  end
84
102
 
103
+ private
104
+
85
105
  def matches_action?(action)
86
106
  @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
87
107
  end
@@ -91,61 +111,29 @@ module CanCan
91
111
  end
92
112
 
93
113
  def matches_subject_class?(subject)
94
- @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
114
+ SubjectClassMatcher.matches_subject_class?(@subjects, subject)
95
115
  end
96
116
 
97
- # Checks if the given subject matches the given conditions hash.
98
- # This behavior can be overriden by a model adapter by defining two class methods:
99
- # override_matching_for_conditions?(subject, conditions) and
100
- # matches_conditions_hash?(subject, conditions)
101
- def matches_conditions_hash?(subject, conditions = @conditions)
102
- return true if conditions.empty?
103
- adapter = model_adapter(subject)
104
-
105
- if adapter.override_conditions_hash_matching?(subject, conditions)
106
- return adapter.matches_conditions_hash?(subject, conditions)
107
- end
108
-
109
- conditions.all? do |name, value|
110
- if adapter.override_condition_matching?(subject, name, value)
111
- return adapter.matches_condition?(subject, name, value)
112
- end
113
-
114
- condition_match?(subject.send(name), value)
115
- end
117
+ def parse_attributes_from_extra_args(args)
118
+ attributes = args.shift if valid_attribute_param?(args.first)
119
+ extra_args = args.shift
120
+ [attributes, extra_args]
116
121
  end
117
122
 
118
- def nested_subject_matches_conditions?(subject_hash)
119
- parent, child = subject_hash.first
120
- matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
121
- end
122
-
123
- def call_block_with_all(action, subject, extra_args)
124
- if subject.class == Class
125
- @block.call(action, subject, nil, *extra_args)
126
- else
127
- @block.call(action, subject.class, subject, *extra_args)
128
- end
129
- end
130
-
131
- def model_adapter(subject)
132
- CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
133
- end
123
+ def condition_and_block_check(conditions, block, action, subject)
124
+ return unless conditions.is_a?(Hash) && block
134
125
 
135
- def condition_match?(attribute, value)
136
- case value
137
- when Hash then hash_condition_match?(attribute, value)
138
- when String then attribute == value
139
- when Enumerable then value.include?(attribute)
140
- else attribute == value
141
- end
126
+ raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
127
+ "Check \":#{action} #{subject}\" ability."
142
128
  end
143
129
 
144
- def hash_condition_match?(attribute, value)
145
- if attribute.kind_of?(Array) || (defined?(ActiveRecord) && attribute.kind_of?(ActiveRecord::Relation))
146
- attribute.any? { |element| matches_conditions_hash?(element, value) }
130
+ def wrap(object)
131
+ if object.nil?
132
+ []
133
+ elsif object.respond_to?(:to_ary)
134
+ object.to_ary || [object]
147
135
  else
148
- !attribute.nil? && matches_conditions_hash?(attribute, value)
136
+ [object]
149
137
  end
150
138
  end
151
139
  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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StiDetector
4
+ def self.sti_class?(subject)
5
+ return false unless defined?(ActiveRecord::Base)
6
+ return false unless subject.respond_to?(:descends_from_active_record?)
7
+ return false if subject == :all || subject.descends_from_active_record?
8
+ return false unless subject < ActiveRecord::Base
9
+
10
+ true
11
+ end
12
+ 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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
- VERSION = "1.10.0"
4
+ VERSION = '3.5.0'.freeze
3
5
  end
data/lib/cancan.rb CHANGED
@@ -1,25 +1,29 @@
1
- require "cancan/version"
1
+ # frozen_string_literal: true
2
+
3
+ require 'cancan/version'
4
+ require 'cancan/config'
5
+ require 'cancan/parameter_validators'
2
6
  require 'cancan/ability'
3
7
  require 'cancan/rule'
4
8
  require 'cancan/controller_resource'
5
9
  require 'cancan/controller_additions'
6
10
  require 'cancan/model_additions'
7
11
  require 'cancan/exceptions'
8
- require 'cancan/inherited_resource'
9
12
 
10
13
  require 'cancan/model_adapters/abstract_adapter'
11
14
  require 'cancan/model_adapters/default_adapter'
15
+ require 'cancan/rules_compressor'
12
16
 
13
17
  if defined? ActiveRecord
18
+ require 'cancan/model_adapters/conditions_extractor'
19
+ require 'cancan/model_adapters/conditions_normalizer'
20
+ require 'cancan/model_adapters/sti_normalizer'
14
21
  require 'cancan/model_adapters/active_record_adapter'
15
- if ActiveRecord.respond_to?(:version) &&
16
- ActiveRecord.version >= Gem::Version.new("4")
17
- require 'cancan/model_adapters/active_record_4_adapter'
18
- else
19
- require 'cancan/model_adapters/active_record_3_adapter'
20
- end
22
+ require 'cancan/model_adapters/active_record_4_adapter'
23
+ require 'cancan/model_adapters/active_record_5_adapter'
24
+ require 'cancan/model_adapters/strategies/base'
25
+ require 'cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery'
26
+ require 'cancan/model_adapters/strategies/joined_alias_exists_subquery'
27
+ require 'cancan/model_adapters/strategies/left_join'
28
+ require 'cancan/model_adapters/strategies/subquery'
21
29
  end
22
-
23
- require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
24
- require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
25
- require 'cancan/model_adapters/sequel_adapter' if defined? Sequel
data/lib/cancancan.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cancan'
2
4
 
3
5
  module CanCanCan
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cancan
2
4
  module Generators
3
5
  class AbilityGenerator < Rails::Generators::Base
4
- source_root File.expand_path('../templates', __FILE__)
6
+ source_root File.expand_path('templates', __dir__)
5
7
 
6
8
  def generate_ability
7
- copy_file "ability.rb", "app/models/ability.rb"
9
+ copy_file 'ability.rb', 'app/models/ability.rb'
8
10
  end
9
11
  end
10
12
  end