cancancan 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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