cancancan 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.rdoc +427 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +23 -0
- data/LICENSE +20 -0
- data/README.rdoc +161 -0
- data/Rakefile +18 -0
- data/init.rb +1 -0
- data/lib/cancan.rb +13 -0
- data/lib/cancan/ability.rb +324 -0
- data/lib/cancan/controller_additions.rb +397 -0
- data/lib/cancan/controller_resource.rb +286 -0
- data/lib/cancan/exceptions.rb +50 -0
- data/lib/cancan/inherited_resource.rb +20 -0
- data/lib/cancan/matchers.rb +14 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
- data/lib/cancan/model_adapters/default_adapter.rb +7 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
- data/lib/cancan/model_additions.rb +31 -0
- data/lib/cancan/rule.rb +147 -0
- data/lib/cancancan.rb +1 -0
- data/lib/generators/cancan/ability/USAGE +4 -0
- data/lib/generators/cancan/ability/ability_generator.rb +11 -0
- data/lib/generators/cancan/ability/templates/ability.rb +32 -0
- data/spec/README.rdoc +28 -0
- data/spec/cancan/ability_spec.rb +455 -0
- data/spec/cancan/controller_additions_spec.rb +141 -0
- data/spec/cancan/controller_resource_spec.rb +553 -0
- data/spec/cancan/exceptions_spec.rb +58 -0
- data/spec/cancan/inherited_resource_spec.rb +60 -0
- data/spec/cancan/matchers_spec.rb +29 -0
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
- data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
- data/spec/cancan/rule_spec.rb +52 -0
- data/spec/matchers.rb +13 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +77 -0
- 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
|