cancancan 1.15.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +38 -0
  3. data/.rubocop_todo.yml +48 -0
  4. data/.travis.yml +8 -2
  5. data/Appraisals +1 -0
  6. data/CHANGELOG.rdoc +5 -0
  7. data/Gemfile +1 -1
  8. data/README.md +58 -41
  9. data/Rakefile +7 -3
  10. data/cancancan.gemspec +13 -12
  11. data/gemfiles/activerecord_4.2.gemfile +1 -0
  12. data/lib/cancan.rb +2 -2
  13. data/lib/cancan/ability.rb +26 -24
  14. data/lib/cancan/controller_additions.rb +33 -23
  15. data/lib/cancan/controller_resource.rb +83 -56
  16. data/lib/cancan/exceptions.rb +1 -1
  17. data/lib/cancan/matchers.rb +2 -2
  18. data/lib/cancan/model_adapters/abstract_adapter.rb +8 -8
  19. data/lib/cancan/model_adapters/active_record_4_adapter.rb +48 -35
  20. data/lib/cancan/model_adapters/active_record_adapter.rb +18 -17
  21. data/lib/cancan/model_adapters/mongoid_adapter.rb +26 -21
  22. data/lib/cancan/model_adapters/sequel_adapter.rb +12 -12
  23. data/lib/cancan/model_additions.rb +0 -1
  24. data/lib/cancan/rule.rb +23 -17
  25. data/lib/cancan/version.rb +1 -1
  26. data/lib/generators/cancan/ability/ability_generator.rb +1 -1
  27. data/spec/cancan/ability_spec.rb +189 -180
  28. data/spec/cancan/controller_additions_spec.rb +77 -64
  29. data/spec/cancan/controller_resource_spec.rb +230 -228
  30. data/spec/cancan/exceptions_spec.rb +20 -20
  31. data/spec/cancan/inherited_resource_spec.rb +21 -21
  32. data/spec/cancan/matchers_spec.rb +12 -12
  33. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +38 -32
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +155 -145
  35. data/spec/cancan/model_adapters/default_adapter_spec.rb +2 -2
  36. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +87 -88
  37. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +44 -47
  38. data/spec/cancan/rule_spec.rb +18 -18
  39. data/spec/spec_helper.rb +2 -2
  40. data/spec/support/ability.rb +0 -1
  41. metadata +60 -19
@@ -6,62 +6,75 @@ module CanCan
6
6
  model_class <= ActiveRecord::Base
7
7
  end
8
8
 
9
- private
10
-
11
- # As of rails 4, `includes()` no longer causes active record to
12
- # look inside the where clause to decide to outer join tables
13
- # you're using in the where. Instead, `references()` is required
14
- # in addition to `includes()` to force the outer join.
15
- def build_relation(*where_conditions)
16
- relation = @model_class.where(*where_conditions)
17
- relation = relation.includes(joins).references(joins) if joins.present?
18
- relation
19
- end
20
-
21
- def self.override_condition_matching?(subject, name, value)
9
+ # TODO: this should be private
10
+ def self.override_condition_matching?(subject, name, _value)
22
11
  # ActiveRecord introduced enums in version 4.1.
23
12
  (ActiveRecord::VERSION::MAJOR > 4 || ActiveRecord::VERSION::MINOR >= 1) &&
24
13
  subject.class.defined_enums.include?(name.to_s)
25
14
  end
26
15
 
16
+ # TODO: this should be private
27
17
  def self.matches_condition?(subject, name, value)
28
18
  # Get the mapping from enum strings to values.
29
19
  enum = subject.class.send(name.to_s.pluralize)
30
20
  # Get the value of the attribute as an integer.
31
21
  attribute = enum[subject.send(name)]
32
22
  # Check to see if the value matches the condition.
33
- value.is_a?(Enumerable) ?
34
- (value.include? attribute) :
23
+ if value.is_a?(Enumerable)
24
+ value.include? attribute
25
+ else
35
26
  attribute == value
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ # As of rails 4, `includes()` no longer causes active record to
33
+ # look inside the where clause to decide to outer join tables
34
+ # you're using in the where. Instead, `references()` is required
35
+ # in addition to `includes()` to force the outer join.
36
+ def build_relation(*where_conditions)
37
+ relation = @model_class.where(*where_conditions)
38
+ relation = relation.includes(joins).references(joins) if joins.present?
39
+ relation
36
40
  end
37
41
 
38
42
  # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
39
43
  def sanitize_sql(conditions)
40
- if ActiveRecord::VERSION::MAJOR > 4 && Hash === conditions
41
- table = @model_class.send(:arel_table)
42
- table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
43
- predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
44
+ if ActiveRecord::VERSION::MAJOR > 4 && conditions.is_a?(Hash)
45
+ sanitize_sql_activerecord5(conditions)
46
+ elsif ActiveRecord::VERSION::MINOR >= 2 && conditions.is_a?(Hash)
47
+ sanitize_sql_activerecord4(conditions)
48
+
49
+ else
50
+ @model_class.send(:sanitize_sql, conditions)
51
+ end
52
+ end
44
53
 
45
- conditions = predicate_builder.resolve_column_aliases(conditions)
46
- conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
54
+ def sanitize_sql_activerecord4(conditions)
55
+ table = Arel::Table.new(@model_class.send(:table_name))
47
56
 
48
- conditions.stringify_keys!
57
+ conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions
58
+ conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
49
59
 
50
- predicate_builder.build_from_hash(conditions).map { |b|
51
- @model_class.send(:connection).visitor.compile b
52
- }.join(' AND ')
53
- elsif ActiveRecord::VERSION::MINOR >= 2 && Hash === conditions
54
- table = Arel::Table.new(@model_class.send(:table_name))
60
+ ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map do |b|
61
+ @model_class.send(:connection).visitor.compile b
62
+ end.join(' AND ')
63
+ end
55
64
 
56
- conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions
57
- conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
65
+ def sanitize_sql_activerecord5(conditions)
66
+ table = @model_class.send(:arel_table)
67
+ table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
68
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
58
69
 
59
- ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map { |b|
60
- @model_class.send(:connection).visitor.compile b
61
- }.join(' AND ')
62
- else
63
- @model_class.send(:sanitize_sql, conditions)
64
- end
70
+ conditions = predicate_builder.resolve_column_aliases(conditions)
71
+ conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions)
72
+
73
+ conditions.stringify_keys!
74
+
75
+ predicate_builder.build_from_hash(conditions).map do |b|
76
+ @model_class.send(:connection).visitor.compile b
77
+ end.join(' AND ')
65
78
  end
66
79
  end
67
80
  end
@@ -28,13 +28,13 @@ module CanCan
28
28
  end
29
29
 
30
30
  def tableized_conditions(conditions, model_class = @model_class)
31
- return conditions unless conditions.kind_of? Hash
32
- conditions.inject({}) do |result_hash, (name, value)|
33
- if value.kind_of? Hash
31
+ return conditions unless conditions.is_a? Hash
32
+ conditions.each_with_object({}) do |(name, value), result_hash|
33
+ if value.is_a? Hash
34
34
  value = value.dup
35
35
  association_class = model_class.reflect_on_association(name).klass.name.constantize
36
- nested = value.inject({}) do |nested,(k,v)|
37
- if v.kind_of? Hash
36
+ nested_resulted = value.each_with_object({}) do |(k, v), nested|
37
+ if v.is_a? Hash
38
38
  value.delete(k)
39
39
  nested[k] = v
40
40
  else
@@ -42,7 +42,7 @@ module CanCan
42
42
  end
43
43
  nested
44
44
  end
45
- result_hash.merge!(tableized_conditions(nested,association_class))
45
+ result_hash.merge!(tableized_conditions(nested_resulted, association_class))
46
46
  else
47
47
  result_hash[name] = value
48
48
  end
@@ -67,28 +67,29 @@ module CanCan
67
67
  if mergeable_conditions?
68
68
  build_relation(conditions)
69
69
  else
70
- build_relation(*(@rules.map(&:conditions)))
70
+ build_relation(*@rules.map(&:conditions))
71
71
  end
72
72
  else
73
- @model_class.all(:conditions => conditions, :joins => joins)
73
+ @model_class.all(conditions: conditions, joins: joins)
74
74
  end
75
75
  end
76
76
 
77
77
  private
78
78
 
79
79
  def mergeable_conditions?
80
- @rules.find {|rule| rule.unmergeable? }.blank?
80
+ @rules.find(&:unmergeable?).blank?
81
81
  end
82
82
 
83
83
  def override_scope
84
84
  conditions = @rules.map(&:conditions).compact
85
- if defined?(ActiveRecord::Relation) && conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
86
- if conditions.size == 1
87
- conditions.first
88
- else
89
- rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
90
- raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
91
- end
85
+ return unless defined?(ActiveRecord::Relation) && conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
86
+ if conditions.size == 1
87
+ conditions.first
88
+ else
89
+ rule_found = @rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
90
+ raise Error,
91
+ 'Unable to merge an Active Record scope with other conditions. '\
92
+ "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
92
93
  end
93
94
  end
94
95
 
@@ -135,7 +136,7 @@ module CanCan
135
136
  def clean_joins(joins_hash)
136
137
  joins = []
137
138
  joins_hash.each do |name, nested|
138
- joins << (nested.empty? ? name : {name => clean_joins(nested)})
139
+ joins << (nested.empty? ? name : { name => clean_joins(nested) })
139
140
  end
140
141
  joins
141
142
  end
@@ -6,8 +6,8 @@ module CanCan
6
6
  end
7
7
 
8
8
  def self.override_conditions_hash_matching?(subject, conditions)
9
- conditions.any? do |k,v|
10
- key_is_not_symbol = lambda { !k.kind_of?(Symbol) }
9
+ conditions.any? do |k, _v|
10
+ key_is_not_symbol = -> { !k.is_a?(Symbol) }
11
11
  subject_value_is_array = lambda do
12
12
  subject.respond_to?(k) && subject.send(k).is_a?(Array)
13
13
  end
@@ -19,50 +19,55 @@ module CanCan
19
19
  def self.matches_conditions_hash?(subject, conditions)
20
20
  # To avoid hitting the db, retrieve the raw Mongo selector from
21
21
  # the Mongoid Criteria and use Mongoid::Matchers#matches?
22
- subject.matches?( subject.class.where(conditions).selector )
22
+ subject.matches?(subject.class.where(conditions).selector)
23
23
  end
24
24
 
25
25
  def database_records
26
- if @rules.size == 0
27
- @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
26
+ if @rules.empty?
27
+ @model_class.where(_id: { '$exists' => false, '$type' => 7 }) # return no records in Mongoid
28
28
  elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria)
29
29
  @rules[0].conditions
30
30
  else
31
31
  # we only need to process can rules if
32
32
  # there are no rules with empty conditions
33
- rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior }
34
- process_can_rules = @rules.count == rules.count
33
+ database_records_from_multiple_rules
34
+ end
35
+ end
35
36
 
36
- rules.inject(@model_class.all) do |records, rule|
37
- if process_can_rules && rule.base_behavior
38
- records.or simplify_relations(@model_class, rule.conditions)
39
- elsif !rule.base_behavior
40
- records.excludes simplify_relations(@model_class, rule.conditions)
41
- else
42
- records
43
- end
37
+ def database_records_from_multiple_rules
38
+ rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior }
39
+ process_can_rules = @rules.count == rules.count
40
+
41
+ rules.inject(@model_class.all) do |records, rule|
42
+ if process_can_rules && rule.base_behavior
43
+ records.or simplify_relations(@model_class, rule.conditions)
44
+ elsif !rule.base_behavior
45
+ records.excludes simplify_relations(@model_class, rule.conditions)
46
+ else
47
+ records
44
48
  end
45
49
  end
46
50
  end
47
51
 
48
52
  private
53
+
49
54
  # Look for criteria on relations and replace with simple id queries
50
55
  # eg.
51
56
  # {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}}
52
57
  # {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }}
53
- def simplify_relations model_class, conditions
58
+ def simplify_relations(model_class, conditions)
54
59
  model_relations = model_class.relations.with_indifferent_access
55
60
  Hash[
56
- conditions.map {|k,v|
57
- if relation = model_relations[k]
61
+ conditions.map do |k, v|
62
+ if (relation = model_relations[k])
58
63
  relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name]
59
64
  v = simplify_relations(relation_class_name.constantize, v)
60
65
  relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id)
61
66
  k = "#{k}_id"
62
- v = { "$in" => relation_ids }
67
+ v = { '$in' => relation_ids }
63
68
  end
64
- [k,v]
65
- }
69
+ [k, v]
70
+ end
66
71
  ]
67
72
  end
68
73
  end
@@ -9,8 +9,8 @@ module CanCan
9
9
  model_class[id]
10
10
  end
11
11
 
12
- def self.override_condition_matching?(subject, name, value)
13
- value.kind_of?(Hash)
12
+ def self.override_condition_matching?(_subject, _name, value)
13
+ value.is_a?(Hash)
14
14
  end
15
15
 
16
16
  def self.matches_condition?(subject, name, value)
@@ -19,8 +19,8 @@ module CanCan
19
19
  false
20
20
  else
21
21
  value.each do |k, v|
22
- if v.kind_of?(Hash)
23
- return false unless self.matches_condition?(obj, k, v)
22
+ if v.is_a?(Hash)
23
+ return false unless matches_condition?(obj, k, v)
24
24
  elsif obj.send(k) != v
25
25
  return false
26
26
  end
@@ -29,12 +29,12 @@ module CanCan
29
29
  end
30
30
 
31
31
  def database_records
32
- if @rules.size == 0
32
+ if @rules.empty?
33
33
  @model_class.where('1=0')
34
34
  else
35
35
  # only need to process can rules if there are no can rule with empty conditions
36
36
  rules = @rules.reject { |rule| rule.base_behavior && rule.conditions.empty? }
37
- rules.reject! { |rule| rule.base_behavior } if rules.count < @rules.count
37
+ rules.reject!(&:base_behavior) if rules.count < @rules.count
38
38
 
39
39
  can_condition_added = false
40
40
  rules.reverse.inject(@model_class.dataset) do |records, rule|
@@ -56,13 +56,13 @@ module CanCan
56
56
  private
57
57
 
58
58
  def normalize_conditions(conditions, model_class = @model_class)
59
- return conditions unless conditions.kind_of? Hash
60
- conditions.inject({}) do |result_hash, (name, value)|
61
- if value.kind_of? Hash
59
+ return conditions unless conditions.is_a? Hash
60
+ conditions.each_with_object({}) do |(name, value), result_hash|
61
+ if value.is_a? Hash
62
62
  value = value.dup
63
63
  association_class = model_class.association_reflection(name).associated_class
64
- nested = value.inject({}) do |nested, (k, v)|
65
- if v.kind_of?(Hash)
64
+ nested_resulted = value.each_with_object({}) do |(k, v), nested|
65
+ if v.is_a?(Hash)
66
66
  value.delete(k)
67
67
  nested_class = association_class.association_reflection(k).associated_class
68
68
  nested[k] = nested_class.where(normalize_conditions(v, association_class))
@@ -71,7 +71,7 @@ module CanCan
71
71
  end
72
72
  nested
73
73
  end
74
- result_hash[name] = association_class.where(nested)
74
+ result_hash[name] = association_class.where(nested_resulted)
75
75
  else
76
76
  result_hash[name] = value
77
77
  end
@@ -1,5 +1,4 @@
1
1
  module CanCan
2
-
3
2
  # This module adds the accessible_by class method to a model. It is included in the model adapters.
4
3
  module ModelAdditions
5
4
  module ClassMethods
@@ -11,7 +11,9 @@ module CanCan
11
11
  # and subject respectively (such as :read, @project). The third argument is a hash
12
12
  # of conditions and the last one is the block passed to the "can" call.
13
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?
14
+ both_block_and_hash_error = 'You are not able to supply a block with a hash of conditions in '\
15
+ "#{action} #{subject} ability. Use either one."
16
+ raise Error, both_block_and_hash_error if conditions.is_a?(Hash) && block
15
17
  @match_all = action.nil? && subject.nil?
16
18
  @base_behavior = base_behavior
17
19
  @actions = [action].flatten
@@ -32,9 +34,9 @@ module CanCan
32
34
  call_block_with_all(action, subject, extra_args)
33
35
  elsif @block && !subject_class?(subject)
34
36
  @block.call(subject, *extra_args)
35
- elsif @conditions.kind_of?(Hash) && subject.class == Hash
37
+ elsif @conditions.is_a?(Hash) && subject.class == Hash
36
38
  nested_subject_matches_conditions?(subject)
37
- elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
39
+ elsif @conditions.is_a?(Hash) && !subject_class?(subject)
38
40
  matches_conditions_hash?(subject)
39
41
  else
40
42
  # Don't stop at "cannot" definitions when there are conditions.
@@ -43,11 +45,11 @@ module CanCan
43
45
  end
44
46
 
45
47
  def only_block?
46
- conditions_empty? && !@block.nil?
48
+ conditions_empty? && @block
47
49
  end
48
50
 
49
51
  def only_raw_sql?
50
- @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
52
+ @block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
51
53
  end
52
54
 
53
55
  def conditions_empty?
@@ -56,29 +58,33 @@ module CanCan
56
58
 
57
59
  def unmergeable?
58
60
  @conditions.respond_to?(:keys) && @conditions.present? &&
59
- (!@conditions.keys.first.kind_of? Symbol)
61
+ (!@conditions.keys.first.is_a? Symbol)
60
62
  end
61
63
 
62
64
  def associations_hash(conditions = @conditions)
63
65
  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
66
+ if conditions.is_a? Hash
67
+ conditions.map do |name, value|
68
+ hash[name] = associations_hash(value) if value.is_a? Hash
69
+ end
70
+ end
67
71
  hash
68
72
  end
69
73
 
70
74
  def attributes_from_conditions
71
75
  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
76
+ if @conditions.is_a? Hash
77
+ @conditions.each do |key, value|
78
+ attributes[key] = value unless [Array, Range, Hash].include? value.class
79
+ end
80
+ end
75
81
  attributes
76
82
  end
77
83
 
78
84
  private
79
85
 
80
86
  def subject_class?(subject)
81
- klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
87
+ klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
82
88
  klass == Class || klass == Module
83
89
  end
84
90
 
@@ -92,9 +98,9 @@ module CanCan
92
98
 
93
99
  def matches_subject_class?(subject)
94
100
  @subjects.any? do |sub|
95
- sub.kind_of?(Module) && (subject.kind_of?(sub) ||
101
+ sub.is_a?(Module) && (subject.is_a?(sub) ||
96
102
  subject.class.to_s == sub.to_s ||
97
- (subject.kind_of?(Module) && subject.ancestors.include?(sub)))
103
+ (subject.is_a?(Module) && subject.ancestors.include?(sub)))
98
104
  end
99
105
  end
100
106
 
@@ -147,10 +153,10 @@ module CanCan
147
153
  end
148
154
 
149
155
  def hash_condition_match?(attribute, value)
150
- if attribute.kind_of?(Array) || (defined?(ActiveRecord) && attribute.kind_of?(ActiveRecord::Relation))
156
+ if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
151
157
  attribute.any? { |element| matches_conditions_hash?(element, value) }
152
158
  else
153
- !attribute.nil? && matches_conditions_hash?(attribute, value)
159
+ attribute && matches_conditions_hash?(attribute, value)
154
160
  end
155
161
  end
156
162
  end
@@ -1,3 +1,3 @@
1
1
  module CanCan
2
- VERSION = "1.15.0"
2
+ VERSION = '1.16.0'.freeze
3
3
  end