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
@@ -1,5 +1,4 @@
1
1
  module CanCan
2
-
3
2
  # This module is automatically included into all controllers.
4
3
  # It also makes the "can?" and "cannot?" methods available to all views.
5
4
  module ControllerAdditions
@@ -72,8 +71,8 @@ module CanCan
72
71
  # Load this resource through another one. This should match the name of the parent instance variable or method.
73
72
  #
74
73
  # [:+through_association+]
75
- # The name of the association to fetch the child records through the parent resource. This is normally not needed
76
- # because it defaults to the pluralized resource name.
74
+ # The name of the association to fetch the child records through the parent resource.
75
+ # This is normally not needed because it defaults to the pluralized resource name.
77
76
  #
78
77
  # [:+shallow+]
79
78
  # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
@@ -82,8 +81,8 @@ module CanCan
82
81
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
83
82
  #
84
83
  # [:+parent+]
85
- # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
86
- # name is given which does not match the controller.
84
+ # True or false depending on if the resource is considered a parent resource.
85
+ # This defaults to +true+ if a resource name is given which does not match the controller.
87
86
  #
88
87
  # [:+class+]
89
88
  # The class to use for the model (string or constant).
@@ -160,8 +159,8 @@ module CanCan
160
159
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
161
160
  #
162
161
  # [:+parent+]
163
- # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
164
- # name is given which does not match the controller.
162
+ # True or false depending on if the resource is considered a parent resource.
163
+ # This defaults to +true+ if a resource name is given which does not match the controller.
165
164
  #
166
165
  # [:+class+]
167
166
  # The class to use for the model (string or constant). This passed in when the instance variable is not set.
@@ -227,7 +226,8 @@ module CanCan
227
226
  end
228
227
 
229
228
  # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
230
- # If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised.
229
+ # If neither of these authorization methods are called,
230
+ # a CanCan::AuthorizationNotPerformed exception will be raised.
231
231
  # This is normally added to the ApplicationController to ensure all controller actions do authorization.
232
232
  #
233
233
  # class ApplicationController < ActionController::Base
@@ -244,26 +244,30 @@ module CanCan
244
244
  # Does not apply to given actions.
245
245
  #
246
246
  # [:+if+]
247
- # Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
247
+ # Supply the name of a controller method to be called.
248
+ # The authorization check only takes place if this returns true.
248
249
  #
249
250
  # check_authorization :if => :admin_controller?
250
251
  #
251
252
  # [:+unless+]
252
- # Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
253
+ # Supply the name of a controller method to be called.
254
+ # The authorization check only takes place if this returns false.
253
255
  #
254
256
  # check_authorization :unless => :devise_controller?
255
257
  #
256
258
  def check_authorization(options = {})
257
- method_name = ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new("4") ? :after_action : :after_filter
259
+ method_name = active_support_4? ? :after_action : :after_filter
258
260
 
259
- block = Proc.new do |controller|
261
+ block = proc do |controller|
260
262
  next if controller.instance_variable_defined?(:@_authorized)
261
263
  next if options[:if] && !controller.send(options[:if])
262
264
  next if options[:unless] && controller.send(options[:unless])
263
- raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
265
+ raise AuthorizationNotPerformed,
266
+ 'This action failed the check_authorization because it does not authorize_resource. '\
267
+ 'Add skip_authorization_check to bypass this check.'
264
268
  end
265
269
 
266
- self.send(method_name, options.slice(:only, :except), &block)
270
+ send(method_name, options.slice(:only, :except), &block)
267
271
  end
268
272
 
269
273
  # Call this in the class of a controller to skip the check_authorization behavior on the actions.
@@ -274,17 +278,19 @@ module CanCan
274
278
  #
275
279
  # Any arguments are passed to the +before_action+ it triggers.
276
280
  def skip_authorization_check(*args)
277
- method_name = ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new("4") ? :before_action : :before_filter
278
- block = Proc.new{ |controller| controller.instance_variable_set(:@_authorized, true) }
279
- self.send(method_name, *args, &block)
281
+ method_name = active_support_4? ? :before_action : :before_filter
282
+ block = proc { |controller| controller.instance_variable_set(:@_authorized, true) }
283
+ send(method_name, *args, &block)
280
284
  end
281
285
 
282
- def skip_authorization(*args)
283
- raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
286
+ def skip_authorization(*_args)
287
+ raise ImplementationRemoved,
288
+ 'The CanCan skip_authorization method has been renamed to skip_authorization_check. '\
289
+ 'Please update your code.'
284
290
  end
285
291
 
286
292
  def cancan_resource_class
287
- if ancestors.map(&:to_s).include? "InheritedResources::Actions"
293
+ if ancestors.map(&:to_s).include? 'InheritedResources::Actions'
288
294
  InheritedResource
289
295
  else
290
296
  ControllerResource
@@ -292,7 +298,11 @@ module CanCan
292
298
  end
293
299
 
294
300
  def cancan_skipper
295
- @_cancan_skipper ||= {:authorize => {}, :load => {}}
301
+ @_cancan_skipper ||= { authorize: {}, load: {} }
302
+ end
303
+
304
+ def active_support_4?
305
+ ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new('4')
296
306
  end
297
307
  end
298
308
 
@@ -342,8 +352,8 @@ module CanCan
342
352
  current_ability.authorize!(*args)
343
353
  end
344
354
 
345
- def unauthorized!(message = nil)
346
- raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
355
+ def unauthorized!(_message = nil)
356
+ raise ImplementationRemoved, 'The unauthorized! method has been removed from CanCan, use authorize! instead.'
347
357
  end
348
358
 
349
359
  # Creates and returns the current user's ability and caches it. If you
@@ -1,5 +1,6 @@
1
1
  module CanCan
2
- # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
2
+ # Handle the load and authorization controller logic
3
+ # so we don't clutter up all controllers with non-interface methods.
3
4
  # This class is used internally, so you do not need to call methods directly on it.
4
5
  class ControllerResource # :nodoc:
5
6
  def self.add_before_action(controller_class, method, *args)
@@ -7,12 +8,13 @@ module CanCan
7
8
  resource_name = args.first
8
9
  before_action_method = before_callback_name(options)
9
10
  controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller|
10
- controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
11
+ controller.class.cancan_resource_class
12
+ .new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
11
13
  end
12
14
  end
13
15
 
14
16
  def self.before_callback_name(options)
15
- if ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new("4")
17
+ if ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new('4')
16
18
  options.delete(:prepend) ? :prepend_before_action : :before_action
17
19
  else
18
20
  options.delete(:prepend) ? :prepend_before_filter : :before_filter
@@ -24,9 +26,12 @@ module CanCan
24
26
  @params = controller.params
25
27
  @options = args.extract_options!
26
28
  @name = args.first
27
- raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
28
- raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
29
- raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
29
+ nested_err = 'The :nested option is no longer supported, instead use :through with separate load/authorize call.'
30
+ raise CanCan::ImplementationRemoved, nested_err if @options[:nested]
31
+ name_err = 'The :name option is no longer supported, instead pass the name as the first argument.'
32
+ raise CanCan::ImplementationRemoved, name_err if @options[:name]
33
+ resource_err = 'The :resource option has been renamed back to :class, use false if no class.'
34
+ raise CanCan::ImplementationRemoved, resource_err if @options[:resource]
30
35
  end
31
36
 
32
37
  def load_and_authorize_resource
@@ -35,31 +40,28 @@ module CanCan
35
40
  end
36
41
 
37
42
  def load_resource
38
- unless skip?(:load)
39
- if load_instance?
40
- self.resource_instance ||= load_resource_instance
41
- elsif load_collection?
42
- self.collection_instance ||= load_collection
43
- end
43
+ return if skip?(:load)
44
+ if load_instance?
45
+ self.resource_instance ||= load_resource_instance
46
+ elsif load_collection?
47
+ self.collection_instance ||= load_collection
44
48
  end
45
49
  end
46
50
 
47
51
  def authorize_resource
48
- unless skip?(:authorize)
49
- @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
50
- end
52
+ return if skip?(:authorize)
53
+ @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
51
54
  end
52
55
 
53
56
  def parent?
54
- @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
57
+ @options.key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
55
58
  end
56
59
 
57
60
  def skip?(behavior)
58
- return false unless options = @controller.class.cancan_skipper[behavior][@name]
59
-
61
+ return false unless (options = @controller.class.cancan_skipper[behavior][@name])
60
62
  options == {} ||
61
- options[:except] && !action_exists_in?(options[:except]) ||
62
- action_exists_in?(options[:only])
63
+ options[:except] && !action_exists_in?(options[:except]) ||
64
+ action_exists_in?(options[:only])
63
65
  end
64
66
 
65
67
  protected
@@ -98,7 +100,7 @@ module CanCan
98
100
  end
99
101
 
100
102
  def initial_attributes
101
- current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
103
+ current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value|
102
104
  resource_params && resource_params.include?(key)
103
105
  end
104
106
  end
@@ -106,18 +108,20 @@ module CanCan
106
108
  def find_resource
107
109
  if @options[:singleton] && parent_resource.respond_to?(name)
108
110
  parent_resource.send(name)
111
+ elsif @options[:find_by]
112
+ find_resource_using_find_by
109
113
  else
110
- if @options[:find_by]
111
- if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
112
- resource_base.send("find_by_#{@options[:find_by]}!", id_param)
113
- elsif resource_base.respond_to? "find_by"
114
- resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
115
- else
116
- resource_base.send(@options[:find_by], id_param)
117
- end
118
- else
119
- adapter.find(resource_base, id_param)
120
- end
114
+ adapter.find(resource_base, id_param)
115
+ end
116
+ end
117
+
118
+ def find_resource_using_find_by
119
+ if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
120
+ resource_base.send("find_by_#{@options[:find_by]}!", id_param)
121
+ elsif resource_base.respond_to? 'find_by'
122
+ resource_base.send('find_by', @options[:find_by].to_sym => id_param)
123
+ else
124
+ resource_base.send(@options[:find_by], id_param)
121
125
  end
122
126
  end
123
127
 
@@ -146,7 +150,9 @@ module CanCan
146
150
  end
147
151
 
148
152
  def member_action?
149
- new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
153
+ new_actions.include?(@params[:action].to_sym) || @options[:singleton] ||
154
+ ((@params[:id] || @params[@options[:id_param]]) &&
155
+ !collection_actions.include?(@params[:action].to_sym))
150
156
  end
151
157
 
152
158
  # Returns the class used for this resource. This can be overriden by the :class option.
@@ -154,15 +160,19 @@ module CanCan
154
160
  # only be used for authorization, not loading since there's no class to load through.
155
161
  def resource_class
156
162
  case @options[:class]
157
- when false then name.to_sym
158
- when nil then namespaced_name.to_s.camelize.constantize
159
- when String then @options[:class].constantize
160
- else @options[:class]
163
+ when false then
164
+ name.to_sym
165
+ when nil then
166
+ namespaced_name.to_s.camelize.constantize
167
+ when String then
168
+ @options[:class].constantize
169
+ else
170
+ @options[:class]
161
171
  end
162
172
  end
163
173
 
164
174
  def resource_class_with_parent
165
- parent_resource ? {parent_resource => resource_class} : resource_class
175
+ parent_resource ? { parent_resource => resource_class } : resource_class
166
176
  end
167
177
 
168
178
  def resource_instance=(instance)
@@ -187,20 +197,33 @@ module CanCan
187
197
  # If the :singleton option is passed it won't use the association because it needs to be handled later.
188
198
  def resource_base
189
199
  if @options[:through]
190
- if parent_resource
191
- base = @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
192
- base = base.scoped if base.respond_to?(:scoped) && defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
193
- base
194
- elsif @options[:shallow]
195
- resource_class
196
- else
197
- raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
198
- end
200
+ resource_base_through
199
201
  else
200
202
  resource_class
201
203
  end
202
204
  end
203
205
 
206
+ def resource_base_through
207
+ if parent_resource
208
+ base = if @options[:singleton]
209
+ resource_class
210
+ else
211
+ parent_resource.send(@options[:through_association] || name.to_s.pluralize)
212
+ end
213
+ base = base.scoped if base.respond_to?(:scoped) && active_record_3?
214
+ base
215
+ elsif @options[:shallow]
216
+ resource_class
217
+ else
218
+ # maybe this should be a record not found error instead?
219
+ raise AccessDenied.new(nil, authorization_action, resource_class)
220
+ end
221
+ end
222
+
223
+ def active_record_3?
224
+ defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
225
+ end
226
+
204
227
  def parent_name
205
228
  @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
206
229
  end
@@ -228,10 +251,13 @@ module CanCan
228
251
 
229
252
  def resource_params
230
253
  if parameters_require_sanitizing? && params_method.present?
231
- return case params_method
232
- when Symbol then @controller.send(params_method)
233
- when String then @controller.instance_eval(params_method)
234
- when Proc then params_method.call(@controller)
254
+ case params_method
255
+ when Symbol then
256
+ @controller.send(params_method)
257
+ when String then
258
+ @controller.instance_eval(params_method)
259
+ when Proc then
260
+ params_method.call(@controller)
235
261
  end
236
262
  else
237
263
  resource_params_by_namespaced_name
@@ -243,9 +269,9 @@ module CanCan
243
269
  end
244
270
 
245
271
  def resource_params_by_namespaced_name
246
- if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
272
+ if @options[:instance_name] && @params.key?(extract_key(@options[:instance_name]))
247
273
  @params[extract_key(@options[:instance_name])]
248
- elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
274
+ elsif @options[:class] && @params.key?(extract_key(@options[:class]))
249
275
  @params[extract_key(@options[:class])]
250
276
  else
251
277
  @params[extract_key(namespaced_name)]
@@ -254,7 +280,8 @@ module CanCan
254
280
 
255
281
  def params_method
256
282
  params_methods.each do |method|
257
- return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc)
283
+ return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
284
+ method.is_a?(String) || method.is_a?(Proc)
258
285
  end
259
286
  nil
260
287
  end
@@ -270,7 +297,7 @@ module CanCan
270
297
  end
271
298
 
272
299
  def namespaced_name
273
- ([namespace, name] * '/').singularize.camelize.safe_constantize || name
300
+ [namespace, name].join('/').singularize.camelize.safe_constantize || name
274
301
  end
275
302
 
276
303
  def name_from_controller
@@ -300,7 +327,7 @@ module CanCan
300
327
  end
301
328
 
302
329
  def extract_key(value)
303
- value.to_s.underscore.gsub('/', '_')
330
+ value.to_s.underscore.tr('/', '_')
304
331
  end
305
332
  end
306
333
  end
@@ -40,7 +40,7 @@ module CanCan
40
40
  @message = message
41
41
  @action = action
42
42
  @subject = subject
43
- @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
43
+ @default_message = I18n.t(:"unauthorized.default", default: 'You are not authorized to access this page.')
44
44
  end
45
45
 
46
46
  def to_s
@@ -14,11 +14,11 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
14
14
 
15
15
  # Check that RSpec is < 2.99
16
16
  if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
17
- alias :failure_message :failure_message_for_should
17
+ alias_method :failure_message, :failure_message_for_should
18
18
  end
19
19
 
20
20
  if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not)
21
- alias :failure_message_when_negated :failure_message_for_should_not
21
+ alias_method :failure_message_when_negated, :failure_message_for_should_not
22
22
  end
23
23
 
24
24
  failure_message do
@@ -11,7 +11,7 @@ module CanCan
11
11
  end
12
12
 
13
13
  # Used to determine if the given adapter should be used for the passed in class.
14
- def self.for_class?(member_class)
14
+ def self.for_class?(_member_class)
15
15
  false # override in subclass
16
16
  end
17
17
 
@@ -22,24 +22,24 @@ module CanCan
22
22
 
23
23
  # Used to determine if this model adapter will override the matching behavior for a hash of conditions.
24
24
  # If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash
25
- def self.override_conditions_hash_matching?(subject, conditions)
25
+ def self.override_conditions_hash_matching?(_subject, _conditions)
26
26
  false
27
27
  end
28
28
 
29
29
  # Override if override_conditions_hash_matching? returns true
30
- def self.matches_conditions_hash?(subject, conditions)
31
- raise NotImplemented, "This model adapter does not support matching on a conditions hash."
30
+ def self.matches_conditions_hash?(_subject, _conditions)
31
+ raise NotImplemented, 'This model adapter does not support matching on a conditions hash.'
32
32
  end
33
33
 
34
34
  # Used to determine if this model adapter will override the matching behavior for a specific condition.
35
35
  # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
36
- def self.override_condition_matching?(subject, name, value)
36
+ def self.override_condition_matching?(_subject, _name, _value)
37
37
  false
38
38
  end
39
39
 
40
40
  # Override if override_condition_matching? returns true
41
- def self.matches_condition?(subject, name, value)
42
- raise NotImplemented, "This model adapter does not support matching on a specific condition."
41
+ def self.matches_condition?(_subject, _name, _value)
42
+ raise NotImplemented, 'This model adapter does not support matching on a specific condition.'
43
43
  end
44
44
 
45
45
  def initialize(model_class, rules)
@@ -49,7 +49,7 @@ module CanCan
49
49
 
50
50
  def database_records
51
51
  # This should be overridden in a subclass to return records which match @rules
52
- raise NotImplemented, "This model adapter does not support fetching records from the database."
52
+ raise NotImplemented, 'This model adapter does not support fetching records from the database.'
53
53
  end
54
54
  end
55
55
  end