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
@@ -1,24 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'controller_resource_loader.rb'
1
4
  module CanCan
2
- # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
5
+ # Handle the load and authorization controller logic
6
+ # so we don't clutter up all controllers with non-interface methods.
3
7
  # This class is used internally, so you do not need to call methods directly on it.
4
8
  class ControllerResource # :nodoc:
5
- def self.add_before_filter(controller_class, method, *args)
9
+ include ControllerResourceLoader
10
+
11
+ def self.add_before_action(controller_class, method, *args)
6
12
  options = args.extract_options!
7
13
  resource_name = args.first
8
- before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
9
- controller_class.send(before_filter_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)
14
+ before_action_method = before_callback_name(options)
15
+ controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller|
16
+ controller.class.cancan_resource_class
17
+ .new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
11
18
  end
12
19
  end
13
20
 
21
+ def self.before_callback_name(options)
22
+ options.delete(:prepend) ? :prepend_before_action : :before_action
23
+ end
24
+
14
25
  def initialize(controller, *args)
15
26
  @controller = controller
16
27
  @params = controller.params
17
28
  @options = args.extract_options!
18
29
  @name = args.first
19
- raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
20
- raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
21
- raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
22
30
  end
23
31
 
24
32
  def load_and_authorize_resource
@@ -26,46 +34,39 @@ module CanCan
26
34
  authorize_resource
27
35
  end
28
36
 
29
- def load_resource
30
- unless skip?(:load)
31
- if load_instance?
32
- self.resource_instance ||= load_resource_instance
33
- elsif load_collection?
34
- self.collection_instance ||= load_collection
35
- end
36
- end
37
- end
38
-
39
37
  def authorize_resource
40
- unless skip?(:authorize)
41
- @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
42
- end
38
+ return if skip?(:authorize)
39
+
40
+ @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
43
41
  end
44
42
 
45
43
  def parent?
46
- @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
44
+ @options.key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
47
45
  end
48
46
 
49
- def skip?(behavior) # This could probably use some refactoring
50
- options = @controller.class.cancan_skipper[behavior][@name]
51
- if options.nil?
52
- false
53
- elsif options == {}
54
- true
55
- elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
56
- true
57
- elsif [options[:only]].flatten.include?(@params[:action].to_sym)
58
- true
59
- end
47
+ def skip?(behavior)
48
+ return false unless (options = @controller.class.cancan_skipper[behavior][@name])
49
+
50
+ options == {} ||
51
+ options[:except] && !action_exists_in?(options[:except]) ||
52
+ action_exists_in?(options[:only])
60
53
  end
61
54
 
62
55
  protected
63
56
 
64
- def load_resource_instance
65
- if !parent? && new_actions.include?(@params[:action].to_sym)
66
- build_resource
67
- elsif id_param || @options[:singleton]
68
- find_resource
57
+ # Returns the class used for this resource. This can be overridden by the :class option.
58
+ # If +false+ is passed in it will use the resource name as a symbol in which case it should
59
+ # only be used for authorization, not loading since there's no class to load through.
60
+ def resource_class
61
+ case @options[:class]
62
+ when false
63
+ name.to_sym
64
+ when nil
65
+ namespaced_name.to_s.camelize.constantize
66
+ when String
67
+ @options[:class].constantize
68
+ else
69
+ @options[:class]
69
70
  end
70
71
  end
71
72
 
@@ -77,85 +78,14 @@ module CanCan
77
78
  resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
78
79
  end
79
80
 
80
- def load_collection
81
- resource_base.accessible_by(current_ability, authorization_action)
82
- end
83
-
84
- def build_resource
85
- resource = resource_base.new(resource_params || {})
86
- assign_attributes(resource)
87
- end
88
-
89
- def assign_attributes(resource)
90
- resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
91
- initial_attributes.each do |attr_name, value|
92
- resource.send("#{attr_name}=", value)
93
- end
94
- resource
95
- end
96
-
97
- def initial_attributes
98
- current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
99
- resource_params && resource_params.include?(key)
100
- end
101
- end
102
-
103
- def find_resource
104
- if @options[:singleton] && parent_resource.respond_to?(name)
105
- parent_resource.send(name)
106
- else
107
- if @options[:find_by]
108
- if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
109
- resource_base.send("find_by_#{@options[:find_by]}!", id_param)
110
- elsif resource_base.respond_to? "find_by"
111
- resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
112
- else
113
- resource_base.send(@options[:find_by], id_param)
114
- end
115
- else
116
- adapter.find(resource_base, id_param)
117
- end
118
- end
119
- end
120
-
121
- def adapter
122
- ModelAdapters::AbstractAdapter.adapter_class(resource_class)
123
- end
124
-
125
- def authorization_action
126
- parent? ? :show : @params[:action].to_sym
127
- end
128
-
129
- def id_param
130
- @params[id_param_key].to_s if @params[id_param_key]
131
- end
132
-
133
- def id_param_key
134
- if @options[:id_param]
135
- @options[:id_param]
136
- else
137
- parent? ? :"#{name}_id" : :id
138
- end
139
- end
140
-
141
81
  def member_action?
142
- new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
143
- end
144
-
145
- # Returns the class used for this resource. This can be overriden by the :class option.
146
- # If +false+ is passed in it will use the resource name as a symbol in which case it should
147
- # only be used for authorization, not loading since there's no class to load through.
148
- def resource_class
149
- case @options[:class]
150
- when false then name.to_sym
151
- when nil then namespaced_name.to_s.camelize.constantize
152
- when String then @options[:class].constantize
153
- else @options[:class]
154
- end
82
+ new_actions.include?(@params[:action].to_sym) || @options[:singleton] ||
83
+ ((@params[:id] || @params[@options[:id_param]]) &&
84
+ !collection_actions.include?(@params[:action].to_sym))
155
85
  end
156
86
 
157
87
  def resource_class_with_parent
158
- parent_resource ? {parent_resource => resource_class} : resource_class
88
+ parent_resource ? { parent_resource => resource_class } : resource_class
159
89
  end
160
90
 
161
91
  def resource_instance=(instance)
@@ -163,7 +93,9 @@ module CanCan
163
93
  end
164
94
 
165
95
  def resource_instance
166
- @controller.instance_variable_get("@#{instance_name}") if load_instance?
96
+ return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
97
+
98
+ @controller.instance_variable_get("@#{instance_name}")
167
99
  end
168
100
 
169
101
  def collection_instance=(instance)
@@ -171,107 +103,15 @@ module CanCan
171
103
  end
172
104
 
173
105
  def collection_instance
174
- @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
175
- end
176
-
177
- # The object that methods (such as "find", "new" or "build") are called on.
178
- # If the :through option is passed it will go through an association on that instance.
179
- # If the :shallow option is passed it will use the resource_class if there's no parent
180
- # If the :singleton option is passed it won't use the association because it needs to be handled later.
181
- def resource_base
182
- if @options[:through]
183
- if parent_resource
184
- base = @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
185
- base = base.scoped if base.respond_to?(:scoped) && defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
186
- base
187
- elsif @options[:shallow]
188
- resource_class
189
- else
190
- raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
191
- end
192
- else
193
- resource_class
194
- end
195
- end
196
-
197
- def parent_name
198
- @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
199
- end
200
-
201
- # The object to load this resource through.
202
- def parent_resource
203
- parent_name && fetch_parent(parent_name)
204
- end
205
-
206
- def fetch_parent(name)
207
- if @controller.instance_variable_defined? "@#{name}"
208
- @controller.instance_variable_get("@#{name}")
209
- elsif @controller.respond_to?(name, true)
210
- @controller.send(name)
211
- end
212
- end
213
-
214
- def current_ability
215
- @controller.send(:current_ability)
216
- end
106
+ return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
217
107
 
218
- def name
219
- @name || name_from_controller
220
- end
221
-
222
- def resource_params
223
- if parameters_require_sanitizing? && params_method.present?
224
- return case params_method
225
- when Symbol then @controller.send(params_method)
226
- when String then @controller.instance_eval(params_method)
227
- when Proc then params_method.call(@controller)
228
- end
229
- else
230
- resource_params_by_namespaced_name
231
- end
108
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
232
109
  end
233
110
 
234
111
  def parameters_require_sanitizing?
235
112
  save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
236
113
  end
237
114
 
238
- def resource_params_by_namespaced_name
239
- if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
240
- @params[extract_key(@options[:instance_name])]
241
- elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
242
- @params[extract_key(@options[:class])]
243
- else
244
- @params[extract_key(namespaced_name)]
245
- end
246
- end
247
-
248
- def params_method
249
- params_methods.each do |method|
250
- return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc)
251
- end
252
- nil
253
- end
254
-
255
- def params_methods
256
- methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
257
- methods.unshift(@options[:param_method]) if @options[:param_method].present?
258
- methods
259
- end
260
-
261
- def namespace
262
- @params[:controller].split(/::|\//)[0..-2]
263
- end
264
-
265
- def namespaced_name
266
- [namespace, name.camelize].flatten.map(&:camelize).join('::').singularize.constantize
267
- rescue NameError
268
- name
269
- end
270
-
271
- def name_from_controller
272
- @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
273
- end
274
-
275
115
  def instance_name
276
116
  @options[:instance_name] || name
277
117
  end
@@ -280,18 +120,22 @@ module CanCan
280
120
  [:index] + Array(@options[:collection])
281
121
  end
282
122
 
283
- def new_actions
284
- [:new, :create] + Array(@options[:new])
285
- end
286
-
287
123
  def save_actions
288
- [:create, :update]
124
+ %i[create update]
289
125
  end
290
126
 
291
127
  private
292
128
 
293
- def extract_key(value)
294
- value.to_s.underscore.gsub('/', '_')
129
+ def action_exists_in?(options)
130
+ Array(options).include?(@params[:action].to_sym)
131
+ end
132
+
133
+ def adapter
134
+ ModelAdapters::AbstractAdapter.adapter_class(resource_class)
135
+ end
136
+
137
+ def current_ability
138
+ @controller.send(:current_ability)
295
139
  end
296
140
  end
297
141
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ControllerResourceBuilder
5
+ protected
6
+
7
+ def build_resource
8
+ resource = resource_base.new(resource_params || {})
9
+ assign_attributes(resource)
10
+ end
11
+
12
+ def assign_attributes(resource)
13
+ resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
14
+ initial_attributes.each do |attr_name, value|
15
+ resource.send("#{attr_name}=", value)
16
+ end
17
+ resource
18
+ end
19
+
20
+ def initial_attributes
21
+ current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value|
22
+ resource_params && resource_params.include?(key)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ControllerResourceFinder
5
+ protected
6
+
7
+ def find_resource
8
+ if @options[:singleton] && parent_resource.respond_to?(name)
9
+ parent_resource.send(name)
10
+ elsif @options[:find_by]
11
+ find_resource_using_find_by
12
+ else
13
+ adapter.find(resource_base, id_param)
14
+ end
15
+ end
16
+
17
+ def find_resource_using_find_by
18
+ find_by_dynamic_finder || find_by_find_by_finder || resource_base.send(@options[:find_by], id_param)
19
+ end
20
+
21
+ def find_by_dynamic_finder
22
+ method_name = "find_by_#{@options[:find_by]}!"
23
+ resource_base.send(method_name, id_param) if resource_base.respond_to? method_name
24
+ end
25
+
26
+ def find_by_find_by_finder
27
+ resource_base.find_by(@options[:find_by].to_sym => id_param) if resource_base.respond_to? :find_by
28
+ end
29
+
30
+ def id_param
31
+ @params[id_param_key].to_s if @params[id_param_key].present?
32
+ end
33
+
34
+ def id_param_key
35
+ if @options[:id_param]
36
+ @options[:id_param]
37
+ else
38
+ parent? ? :"#{name}_id" : :id
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'controller_resource_finder.rb'
4
+ require_relative 'controller_resource_name_finder.rb'
5
+ require_relative 'controller_resource_builder.rb'
6
+ require_relative 'controller_resource_sanitizer.rb'
7
+ module CanCan
8
+ module ControllerResourceLoader
9
+ include CanCan::ControllerResourceNameFinder
10
+ include CanCan::ControllerResourceFinder
11
+ include CanCan::ControllerResourceBuilder
12
+ include CanCan::ControllerResourceSanitizer
13
+
14
+ def load_resource
15
+ return if skip?(:load)
16
+
17
+ if load_instance?
18
+ self.resource_instance ||= load_resource_instance
19
+ elsif load_collection?
20
+ self.collection_instance ||= load_collection
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def new_actions
27
+ %i[new create] + Array(@options[:new])
28
+ end
29
+
30
+ def resource_params_by_key(key)
31
+ return unless @options[key] && @params.key?(extract_key(@options[key]))
32
+
33
+ @params[extract_key(@options[key])]
34
+ end
35
+
36
+ def resource_params_by_namespaced_name
37
+ resource_params_by_key(:instance_name) || resource_params_by_key(:class) || (
38
+ params = @params[extract_key(namespaced_name)]
39
+ params.respond_to?(:to_h) ? params : nil)
40
+ end
41
+
42
+ def resource_params
43
+ if parameters_require_sanitizing? && params_method.present?
44
+ sanitize_parameters
45
+ else
46
+ resource_params_by_namespaced_name
47
+ end
48
+ end
49
+
50
+ def fetch_parent(name)
51
+ if @controller.instance_variable_defined? "@#{name}"
52
+ @controller.instance_variable_get("@#{name}")
53
+ elsif @controller.respond_to?(name, true)
54
+ @controller.send(name)
55
+ end
56
+ end
57
+
58
+ # The object to load this resource through.
59
+ def parent_resource
60
+ parent_name && fetch_parent(parent_name)
61
+ end
62
+
63
+ def parent_name
64
+ @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
65
+ end
66
+
67
+ def resource_base_through_parent_resource
68
+ if @options[:singleton]
69
+ resource_class
70
+ else
71
+ parent_resource.send(@options[:through_association] || name.to_s.pluralize)
72
+ end
73
+ end
74
+
75
+ def resource_base_through
76
+ if parent_resource
77
+ resource_base_through_parent_resource
78
+ elsif @options[:shallow]
79
+ resource_class
80
+ else
81
+ # maybe this should be a record not found error instead?
82
+ raise AccessDenied.new(nil, authorization_action, resource_class)
83
+ end
84
+ end
85
+
86
+ # The object that methods (such as "find", "new" or "build") are called on.
87
+ # If the :through option is passed it will go through an association on that instance.
88
+ # If the :shallow option is passed it will use the resource_class if there's no parent
89
+ # If the :singleton option is passed it won't use the association because it needs to be handled later.
90
+ def resource_base
91
+ @options[:through] ? resource_base_through : resource_class
92
+ end
93
+
94
+ def parent_authorization_action
95
+ @options[:parent_action] || :show
96
+ end
97
+
98
+ def authorization_action
99
+ parent? ? parent_authorization_action : @params[:action].to_sym
100
+ end
101
+
102
+ def load_collection
103
+ resource_base.accessible_by(current_ability, authorization_action)
104
+ end
105
+
106
+ def load_resource_instance
107
+ if !parent? && new_actions.include?(@params[:action].to_sym)
108
+ build_resource
109
+ elsif id_param || @options[:singleton]
110
+ find_resource
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def extract_key(value)
117
+ value.to_s.underscore.tr('/', '_')
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ControllerResourceNameFinder
5
+ protected
6
+
7
+ def name_from_controller
8
+ @params[:controller].split('/').last.singularize
9
+ end
10
+
11
+ def namespaced_name
12
+ [namespace, name].join('/').singularize.camelize.safe_constantize || name
13
+ end
14
+
15
+ def name
16
+ @name || name_from_controller
17
+ end
18
+
19
+ def namespace
20
+ @params[:controller].split('/')[0..-2]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ControllerResourceSanitizer
5
+ protected
6
+
7
+ def sanitize_parameters
8
+ case params_method
9
+ when Symbol
10
+ @controller.send(params_method)
11
+ when String
12
+ @controller.instance_eval(params_method)
13
+ when Proc
14
+ params_method.call(@controller)
15
+ end
16
+ end
17
+
18
+ def params_methods
19
+ methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
20
+ methods.unshift(@options[:param_method]) if @options[:param_method].present?
21
+ methods
22
+ end
23
+
24
+ def params_method
25
+ params_methods.each do |method|
26
+ return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
27
+ method.is_a?(String) || method.is_a?(Proc)
28
+ end
29
+ nil
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  # A general CanCan exception
3
5
  class Error < StandardError; end
@@ -8,9 +10,18 @@ module CanCan
8
10
  # Raised when removed code is called, an alternative solution is provided in message.
9
11
  class ImplementationRemoved < Error; end
10
12
 
11
- # Raised when using check_authorization without calling authorized!
13
+ # Raised when using check_authorization without calling authorize!
12
14
  class AuthorizationNotPerformed < Error; end
13
15
 
16
+ # Raised when a rule is created with both a block and a hash of conditions
17
+ class BlockAndConditionsError < Error; end
18
+
19
+ # Raised when an unexpected argument is passed as an attribute
20
+ class AttributeArgumentError < Error; end
21
+
22
+ # Raised when using a wrong association name
23
+ class WrongAssociationName < Error; end
24
+
14
25
  # This error is raised when a user isn't allowed to access a given controller action.
15
26
  # This usually happens within a call to ControllerAdditions#authorize! but can be
16
27
  # raised manually.
@@ -30,21 +41,30 @@ module CanCan
30
41
  # exception.default_message = "Default error message"
31
42
  # exception.message # => "Default error message"
32
43
  #
33
- # See ControllerAdditions#authorized! for more information on rescuing from this exception
44
+ # See ControllerAdditions#authorize! for more information on rescuing from this exception
34
45
  # and customizing the message using I18n.
35
46
  class AccessDenied < Error
36
- attr_reader :action, :subject
47
+ attr_reader :action, :subject, :conditions
37
48
  attr_writer :default_message
38
49
 
39
- def initialize(message = nil, action = nil, subject = nil)
50
+ def initialize(message = nil, action = nil, subject = nil, conditions = nil)
40
51
  @message = message
41
52
  @action = action
42
53
  @subject = subject
43
- @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
54
+ @conditions = conditions
55
+ @default_message = I18n.t(:"unauthorized.default", default: 'You are not authorized to access this page.')
44
56
  end
45
57
 
46
58
  def to_s
47
59
  @message || @default_message
48
60
  end
61
+
62
+ def inspect
63
+ details = %i[action subject conditions message].map do |attribute|
64
+ value = instance_variable_get "@#{attribute}"
65
+ "#{attribute}: #{value.inspect}" if value.present?
66
+ end.compact.join(', ')
67
+ "#<#{self.class.name} #{details}>"
68
+ end
49
69
  end
50
70
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability
2
4
 
3
5
  if rspec_module == 'RSpec'
@@ -9,13 +11,25 @@ end
9
11
 
10
12
  Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
11
13
  match do |ability|
12
- ability.can?(*args)
14
+ actions = args.first
15
+ if actions.is_a? Array
16
+ if actions.empty?
17
+ false
18
+ else
19
+ actions.all? { |action| ability.can?(action, *args[1..-1]) }
20
+ end
21
+ else
22
+ ability.can?(*args)
23
+ end
13
24
  end
14
25
 
15
26
  # Check that RSpec is < 2.99
16
27
  if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
17
- alias :failure_message :failure_message_for_should
18
- alias :failure_message_when_negated :failure_message_for_should_not
28
+ alias_method :failure_message, :failure_message_for_should
29
+ end
30
+
31
+ if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not)
32
+ alias_method :failure_message_when_negated, :failure_message_for_should_not
19
33
  end
20
34
 
21
35
  failure_message do