cancancan 1.7.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 (42) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.rdoc +427 -0
  3. data/CONTRIBUTING.md +11 -0
  4. data/Gemfile +23 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +161 -0
  7. data/Rakefile +18 -0
  8. data/init.rb +1 -0
  9. data/lib/cancan.rb +13 -0
  10. data/lib/cancan/ability.rb +324 -0
  11. data/lib/cancan/controller_additions.rb +397 -0
  12. data/lib/cancan/controller_resource.rb +286 -0
  13. data/lib/cancan/exceptions.rb +50 -0
  14. data/lib/cancan/inherited_resource.rb +20 -0
  15. data/lib/cancan/matchers.rb +14 -0
  16. data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
  17. data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
  18. data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
  19. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  20. data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
  21. data/lib/cancan/model_additions.rb +31 -0
  22. data/lib/cancan/rule.rb +147 -0
  23. data/lib/cancancan.rb +1 -0
  24. data/lib/generators/cancan/ability/USAGE +4 -0
  25. data/lib/generators/cancan/ability/ability_generator.rb +11 -0
  26. data/lib/generators/cancan/ability/templates/ability.rb +32 -0
  27. data/spec/README.rdoc +28 -0
  28. data/spec/cancan/ability_spec.rb +455 -0
  29. data/spec/cancan/controller_additions_spec.rb +141 -0
  30. data/spec/cancan/controller_resource_spec.rb +553 -0
  31. data/spec/cancan/exceptions_spec.rb +58 -0
  32. data/spec/cancan/inherited_resource_spec.rb +60 -0
  33. data/spec/cancan/matchers_spec.rb +29 -0
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
  35. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
  36. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  37. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
  38. data/spec/cancan/rule_spec.rb +52 -0
  39. data/spec/matchers.rb +13 -0
  40. data/spec/spec.opts +2 -0
  41. data/spec/spec_helper.rb +77 -0
  42. metadata +126 -0
@@ -0,0 +1,397 @@
1
+ module CanCan
2
+
3
+ # This module is automatically included into all controllers.
4
+ # It also makes the "can?" and "cannot?" methods available to all views.
5
+ module ControllerAdditions
6
+ module ClassMethods
7
+ # Sets up a before filter which loads and authorizes the current resource. This performs both
8
+ # load_resource and authorize_resource and accepts the same arguments. See those methods for details.
9
+ #
10
+ # class BooksController < ApplicationController
11
+ # load_and_authorize_resource
12
+ # end
13
+ #
14
+ def load_and_authorize_resource(*args)
15
+ cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
16
+ end
17
+
18
+ # Sets up a before filter which loads the model resource into an instance variable.
19
+ # For example, given an ArticlesController it will load the current article into the @article
20
+ # instance variable. It does this by either calling Article.find(params[:id]) or
21
+ # Article.new(params[:article]) depending upon the action. The index action will
22
+ # automatically set @articles to Article.accessible_by(current_ability).
23
+ #
24
+ # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
25
+ # the initial attributes based on these conditions. This way these actions will satisfy
26
+ # the ability restrictions.
27
+ #
28
+ # Call this method directly on the controller class.
29
+ #
30
+ # class BooksController < ApplicationController
31
+ # load_resource
32
+ # end
33
+ #
34
+ # A resource is not loaded if the instance variable is already set. This makes it easy to override
35
+ # the behavior through a before_filter on certain actions.
36
+ #
37
+ # class BooksController < ApplicationController
38
+ # before_filter :find_book_by_permalink, :only => :show
39
+ # load_resource
40
+ #
41
+ # private
42
+ #
43
+ # def find_book_by_permalink
44
+ # @book = Book.find_by_permalink!(params[:id)
45
+ # end
46
+ # end
47
+ #
48
+ # If a name is provided which does not match the controller it assumes it is a parent resource. Child
49
+ # resources can then be loaded through it.
50
+ #
51
+ # class BooksController < ApplicationController
52
+ # load_resource :author
53
+ # load_resource :book, :through => :author
54
+ # end
55
+ #
56
+ # Here the author resource will be loaded before each action using params[:author_id]. The book resource
57
+ # will then be loaded through the @author instance variable.
58
+ #
59
+ # That first argument is optional and will default to the singular name of the controller.
60
+ # A hash of options (see below) can also be passed to this method to further customize it.
61
+ #
62
+ # See load_and_authorize_resource to automatically authorize the resource too.
63
+ #
64
+ # Options:
65
+ # [:+only+]
66
+ # Only applies before filter to given actions.
67
+ #
68
+ # [:+except+]
69
+ # Does not apply before filter to given actions.
70
+ #
71
+ # [:+through+]
72
+ # Load this resource through another one. This should match the name of the parent instance variable or method.
73
+ #
74
+ # [:+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.
77
+ #
78
+ # [:+shallow+]
79
+ # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
80
+ #
81
+ # [:+singleton+]
82
+ # Pass +true+ if this is a singleton resource through a +has_one+ association.
83
+ #
84
+ # [:+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.
87
+ #
88
+ # [:+class+]
89
+ # The class to use for the model (string or constant).
90
+ #
91
+ # [:+instance_name+]
92
+ # The name of the instance variable to load the resource into.
93
+ #
94
+ # [:+find_by+]
95
+ # Find using a different attribute other than id. For example.
96
+ #
97
+ # load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
98
+ #
99
+ # [:+id_param+]
100
+ # Find using a param key other than :id. For example:
101
+ #
102
+ # load_resource :id_param => :url # will use find(params[:url])
103
+ #
104
+ # [:+collection+]
105
+ # Specify which actions are resource collection actions in addition to :+index+. This
106
+ # is usually not necessary because it will try to guess depending on if the id param is present.
107
+ #
108
+ # load_resource :collection => [:sort, :list]
109
+ #
110
+ # [:+new+]
111
+ # Specify which actions are new resource actions in addition to :+new+ and :+create+.
112
+ # Pass an action name into here if you would like to build a new resource instead of
113
+ # fetch one.
114
+ #
115
+ # load_resource :new => :build
116
+ #
117
+ # [:+prepend+]
118
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
119
+ #
120
+ def load_resource(*args)
121
+ cancan_resource_class.add_before_filter(self, :load_resource, *args)
122
+ end
123
+
124
+ # Sets up a before filter which authorizes the resource using the instance variable.
125
+ # For example, if you have an ArticlesController it will check the @article instance variable
126
+ # and ensure the user can perform the current action on it. Under the hood it is doing
127
+ # something like the following.
128
+ #
129
+ # authorize!(params[:action].to_sym, @article || Article)
130
+ #
131
+ # Call this method directly on the controller class.
132
+ #
133
+ # class BooksController < ApplicationController
134
+ # authorize_resource
135
+ # end
136
+ #
137
+ # If you pass in the name of a resource which does not match the controller it will assume
138
+ # it is a parent resource.
139
+ #
140
+ # class BooksController < ApplicationController
141
+ # authorize_resource :author
142
+ # authorize_resource :book
143
+ # end
144
+ #
145
+ # Here it will authorize :+show+, @+author+ on every action before authorizing the book.
146
+ #
147
+ # That first argument is optional and will default to the singular name of the controller.
148
+ # A hash of options (see below) can also be passed to this method to further customize it.
149
+ #
150
+ # See load_and_authorize_resource to automatically load the resource too.
151
+ #
152
+ # Options:
153
+ # [:+only+]
154
+ # Only applies before filter to given actions.
155
+ #
156
+ # [:+except+]
157
+ # Does not apply before filter to given actions.
158
+ #
159
+ # [:+singleton+]
160
+ # Pass +true+ if this is a singleton resource through a +has_one+ association.
161
+ #
162
+ # [:+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.
165
+ #
166
+ # [:+class+]
167
+ # The class to use for the model (string or constant). This passed in when the instance variable is not set.
168
+ # Pass +false+ if there is no associated class for this resource and it will use a symbol of the resource name.
169
+ #
170
+ # [:+instance_name+]
171
+ # The name of the instance variable for this resource.
172
+ #
173
+ # [:+through+]
174
+ # Authorize conditions on this parent resource when instance isn't available.
175
+ #
176
+ # [:+prepend+]
177
+ # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
178
+ #
179
+ def authorize_resource(*args)
180
+ cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
181
+ end
182
+
183
+ # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
184
+ # useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions
185
+ # to skip the effects on. It will apply to all actions by default.
186
+ #
187
+ # class ProjectsController < SomeOtherController
188
+ # skip_load_and_authorize_resource :only => :index
189
+ # end
190
+ #
191
+ # You can also pass the resource name as the first argument to skip that resource.
192
+ def skip_load_and_authorize_resource(*args)
193
+ skip_load_resource(*args)
194
+ skip_authorize_resource(*args)
195
+ end
196
+
197
+ # Skip the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
198
+ # only do authorization on certain actions. You can pass :only and :except options to specify which actions to
199
+ # skip the effects on. It will apply to all actions by default.
200
+ #
201
+ # class ProjectsController < ApplicationController
202
+ # load_and_authorize_resource
203
+ # skip_load_resource :only => :index
204
+ # end
205
+ #
206
+ # You can also pass the resource name as the first argument to skip that resource.
207
+ def skip_load_resource(*args)
208
+ options = args.extract_options!
209
+ name = args.first
210
+ cancan_skipper[:load][name] = options
211
+ end
212
+
213
+ # Skip the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to
214
+ # only do loading on certain actions. You can pass :only and :except options to specify which actions to
215
+ # skip the effects on. It will apply to all actions by default.
216
+ #
217
+ # class ProjectsController < ApplicationController
218
+ # load_and_authorize_resource
219
+ # skip_authorize_resource :only => :index
220
+ # end
221
+ #
222
+ # You can also pass the resource name as the first argument to skip that resource.
223
+ def skip_authorize_resource(*args)
224
+ options = args.extract_options!
225
+ name = args.first
226
+ cancan_skipper[:authorize][name] = options
227
+ end
228
+
229
+ # 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.
231
+ # This is normally added to the ApplicationController to ensure all controller actions do authorization.
232
+ #
233
+ # class ApplicationController < ActionController::Base
234
+ # check_authorization
235
+ # end
236
+ #
237
+ # See skip_authorization_check to bypass this check on specific controller actions.
238
+ #
239
+ # Options:
240
+ # [:+only+]
241
+ # Only applies to given actions.
242
+ #
243
+ # [:+except+]
244
+ # Does not apply to given actions.
245
+ #
246
+ # [:+if+]
247
+ # Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
248
+ #
249
+ # check_authorization :if => :admin_controller?
250
+ #
251
+ # [:+unless+]
252
+ # Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
253
+ #
254
+ # check_authorization :unless => :devise_controller?
255
+ #
256
+ def check_authorization(options = {})
257
+ self.after_filter(options.slice(:only, :except)) do |controller|
258
+ next if controller.instance_variable_defined?(:@_authorized)
259
+ next if options[:if] && !controller.send(options[:if])
260
+ next if options[:unless] && controller.send(options[:unless])
261
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
262
+ end
263
+ end
264
+
265
+ # Call this in the class of a controller to skip the check_authorization behavior on the actions.
266
+ #
267
+ # class HomeController < ApplicationController
268
+ # skip_authorization_check :only => :index
269
+ # end
270
+ #
271
+ # Any arguments are passed to the +before_filter+ it triggers.
272
+ def skip_authorization_check(*args)
273
+ self.before_filter(*args) do |controller|
274
+ controller.instance_variable_set(:@_authorized, true)
275
+ end
276
+ end
277
+
278
+ def skip_authorization(*args)
279
+ raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
280
+ end
281
+
282
+ def cancan_resource_class
283
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
284
+ InheritedResource
285
+ else
286
+ ControllerResource
287
+ end
288
+ end
289
+
290
+ def cancan_skipper
291
+ @_cancan_skipper ||= {:authorize => {}, :load => {}}
292
+ end
293
+ end
294
+
295
+ def self.included(base)
296
+ base.extend ClassMethods
297
+ base.helper_method :can?, :cannot?, :current_ability
298
+ end
299
+
300
+ # Raises a CanCan::AccessDenied exception if the current_ability cannot
301
+ # perform the given action. This is usually called in a controller action or
302
+ # before filter to perform the authorization.
303
+ #
304
+ # def show
305
+ # @article = Article.find(params[:id])
306
+ # authorize! :read, @article
307
+ # end
308
+ #
309
+ # A :message option can be passed to specify a different message.
310
+ #
311
+ # authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
312
+ #
313
+ # You can also use I18n to customize the message. Action aliases defined in Ability work here.
314
+ #
315
+ # en:
316
+ # unauthorized:
317
+ # manage:
318
+ # all: "Not authorized to %{action} %{subject}."
319
+ # user: "Not allowed to manage other user accounts."
320
+ # update:
321
+ # project: "Not allowed to update this project."
322
+ #
323
+ # You can rescue from the exception in the controller to customize how unauthorized
324
+ # access is displayed to the user.
325
+ #
326
+ # class ApplicationController < ActionController::Base
327
+ # rescue_from CanCan::AccessDenied do |exception|
328
+ # redirect_to root_url, :alert => exception.message
329
+ # end
330
+ # end
331
+ #
332
+ # See the CanCan::AccessDenied exception for more details on working with the exception.
333
+ #
334
+ # See the load_and_authorize_resource method to automatically add the authorize! behavior
335
+ # to the default RESTful actions.
336
+ def authorize!(*args)
337
+ @_authorized = true
338
+ current_ability.authorize!(*args)
339
+ end
340
+
341
+ def unauthorized!(message = nil)
342
+ raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
343
+ end
344
+
345
+ # Creates and returns the current user's ability and caches it. If you
346
+ # want to override how the Ability is defined then this is the place.
347
+ # Just define the method in the controller to change behavior.
348
+ #
349
+ # def current_ability
350
+ # # instead of Ability.new(current_user)
351
+ # @current_ability ||= UserAbility.new(current_account)
352
+ # end
353
+ #
354
+ # Notice it is important to cache the ability object so it is not
355
+ # recreated every time.
356
+ def current_ability
357
+ @current_ability ||= ::Ability.new(current_user)
358
+ end
359
+
360
+ # Use in the controller or view to check the user's permission for a given action
361
+ # and object.
362
+ #
363
+ # can? :destroy, @project
364
+ #
365
+ # You can also pass the class instead of an instance (if you don't have one handy).
366
+ #
367
+ # <% if can? :create, Project %>
368
+ # <%= link_to "New Project", new_project_path %>
369
+ # <% end %>
370
+ #
371
+ # If it's a nested resource, you can pass the parent instance in a hash. This way it will
372
+ # check conditions which reach through that association.
373
+ #
374
+ # <% if can? :create, @category => Project %>
375
+ # <%= link_to "New Project", new_project_path %>
376
+ # <% end %>
377
+ #
378
+ # This simply calls "can?" on the current_ability. See Ability#can?.
379
+ def can?(*args)
380
+ current_ability.can?(*args)
381
+ end
382
+
383
+ # Convenience method which works the same as "can?" but returns the opposite value.
384
+ #
385
+ # cannot? :destroy, @project
386
+ #
387
+ def cannot?(*args)
388
+ current_ability.cannot?(*args)
389
+ end
390
+ end
391
+ end
392
+
393
+ if defined? ActionController::Base
394
+ ActionController::Base.class_eval do
395
+ include CanCan::ControllerAdditions
396
+ end
397
+ end
@@ -0,0 +1,286 @@
1
+ module CanCan
2
+ # Handle the load and authorization controller logic so we don't clutter up all controllers with non-interface methods.
3
+ # This class is used internally, so you do not need to call methods directly on it.
4
+ class ControllerResource # :nodoc:
5
+ def self.add_before_filter(controller_class, method, *args)
6
+ options = args.extract_options!
7
+ 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)
11
+ end
12
+ end
13
+
14
+ def initialize(controller, *args)
15
+ @controller = controller
16
+ @params = controller.params
17
+ @options = args.extract_options!
18
+ @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
+ end
23
+
24
+ def load_and_authorize_resource
25
+ load_resource
26
+ authorize_resource
27
+ end
28
+
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
+ def authorize_resource
40
+ unless skip?(:authorize)
41
+ @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
42
+ end
43
+ end
44
+
45
+ def parent?
46
+ @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
47
+ end
48
+
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
60
+ end
61
+
62
+ protected
63
+
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
69
+ end
70
+ end
71
+
72
+ def load_instance?
73
+ parent? || member_action?
74
+ end
75
+
76
+ def load_collection?
77
+ resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
78
+ end
79
+
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
+ 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
155
+ end
156
+
157
+ def resource_class_with_parent
158
+ parent_resource ? {parent_resource => resource_class} : resource_class
159
+ end
160
+
161
+ def resource_instance=(instance)
162
+ @controller.instance_variable_set("@#{instance_name}", instance)
163
+ end
164
+
165
+ def resource_instance
166
+ @controller.instance_variable_get("@#{instance_name}") if load_instance?
167
+ end
168
+
169
+ def collection_instance=(instance)
170
+ @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
171
+ end
172
+
173
+ 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
217
+
218
+ def name
219
+ @name || name_from_controller
220
+ end
221
+
222
+ def resource_params
223
+ if param_actions.include?(@params[:action].to_sym) && params_method.present?
224
+ return @controller.send(params_method)
225
+ elsif @options[:class]
226
+ params_key = extract_key(@options[:class])
227
+ return @params[params_key] if @params[params_key]
228
+ end
229
+
230
+ resource_params_by_namespaced_name
231
+ end
232
+
233
+ def resource_params_by_namespaced_name
234
+ @params[extract_key(namespaced_name)]
235
+ end
236
+
237
+ def params_method
238
+ params_methods.each do |method|
239
+ return method if @controller.respond_to?(method, true)
240
+ end
241
+ nil
242
+ end
243
+
244
+ def params_methods
245
+ methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
246
+ methods.unshift(@options[:param_method]) if @options[:param_method].present?
247
+ methods
248
+ end
249
+
250
+ def namespace
251
+ @params[:controller].split(/::|\//)[0..-2]
252
+ end
253
+
254
+ def namespaced_name
255
+ [namespace, name.camelize].join('::').singularize.camelize.constantize
256
+ rescue NameError
257
+ name
258
+ end
259
+
260
+ def name_from_controller
261
+ @params[:controller].sub("Controller", "").underscore.split('/').last.singularize
262
+ end
263
+
264
+ def instance_name
265
+ @options[:instance_name] || name
266
+ end
267
+
268
+ def collection_actions
269
+ [:index] + Array(@options[:collection])
270
+ end
271
+
272
+ def new_actions
273
+ [:new, :create] + Array(@options[:new])
274
+ end
275
+
276
+ def param_actions
277
+ [:create, :update]
278
+ end
279
+
280
+ private
281
+
282
+ def extract_key(value)
283
+ value.to_s.underscore.gsub('/', '_')
284
+ end
285
+ end
286
+ end