inherited_resources 0.9.2
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.
- data/CHANGELOG +103 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +524 -0
- data/Rakefile +40 -0
- data/lib/inherited_resources.rb +23 -0
- data/lib/inherited_resources/actions.rb +79 -0
- data/lib/inherited_resources/base.rb +42 -0
- data/lib/inherited_resources/base_helpers.rb +363 -0
- data/lib/inherited_resources/belongs_to_helpers.rb +89 -0
- data/lib/inherited_resources/class_methods.rb +338 -0
- data/lib/inherited_resources/dsl.rb +26 -0
- data/lib/inherited_resources/dumb_responder.rb +20 -0
- data/lib/inherited_resources/has_scope_helpers.rb +83 -0
- data/lib/inherited_resources/legacy/respond_to.rb +156 -0
- data/lib/inherited_resources/legacy/responder.rb +200 -0
- data/lib/inherited_resources/polymorphic_helpers.rb +155 -0
- data/lib/inherited_resources/singleton_helpers.rb +95 -0
- data/lib/inherited_resources/url_helpers.rb +179 -0
- data/test/aliases_test.rb +139 -0
- data/test/association_chain_test.rb +125 -0
- data/test/base_test.rb +225 -0
- data/test/belongs_to_test.rb +87 -0
- data/test/class_methods_test.rb +138 -0
- data/test/customized_base_test.rb +162 -0
- data/test/customized_belongs_to_test.rb +76 -0
- data/test/defaults_test.rb +70 -0
- data/test/flash_test.rb +88 -0
- data/test/has_scope_test.rb +139 -0
- data/test/nested_belongs_to_test.rb +108 -0
- data/test/optional_belongs_to_test.rb +164 -0
- data/test/polymorphic_test.rb +186 -0
- data/test/redirect_to_test.rb +51 -0
- data/test/respond_to_test.rb +155 -0
- data/test/singleton_test.rb +83 -0
- data/test/test_helper.rb +38 -0
- data/test/url_helpers_test.rb +537 -0
- metadata +89 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
module InheritedResources
|
2
|
+
|
3
|
+
# = belongs_to
|
4
|
+
#
|
5
|
+
# Let's suppose that we have some tasks that belongs to projects. To specify
|
6
|
+
# this assoication in your controllers, just do:
|
7
|
+
#
|
8
|
+
# class TasksController < InheritedResources::Base
|
9
|
+
# belongs_to :project
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# belongs_to accepts several options to be able to configure the association.
|
13
|
+
# For example, if you want urls like /projects/:project_title/tasks, you
|
14
|
+
# can customize how InheritedResources find your projects:
|
15
|
+
#
|
16
|
+
# class TasksController < InheritedResources::Base
|
17
|
+
# belongs_to :project, :finder => :find_by_title!, :param => :project_title
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# It also accepts :route_name, :parent_class and :instance_name as options.
|
21
|
+
# Check the lib/inherited_resources/class_methods.rb for more.
|
22
|
+
#
|
23
|
+
# = nested_belongs_to
|
24
|
+
#
|
25
|
+
# Now, our Tasks get some Comments and you need to nest even deeper. Good
|
26
|
+
# practices says that you should never nest more than two resources, but sometimes
|
27
|
+
# you have to for security reasons. So this is an example of how you can do it:
|
28
|
+
#
|
29
|
+
# class CommentsController < InheritedResources::Base
|
30
|
+
# nested_belongs_to :project, :task
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# If you need to configure any of these belongs to, you can nested them using blocks:
|
34
|
+
#
|
35
|
+
# class CommentsController < InheritedResources::Base
|
36
|
+
# belongs_to :project, :finder => :find_by_title!, :param => :project_title do
|
37
|
+
# belongs_to :task
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Warning: calling several belongs_to is the same as nesting them:
|
42
|
+
#
|
43
|
+
# class CommentsController < InheritedResources::Base
|
44
|
+
# belongs_to :project
|
45
|
+
# belongs_to :task
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# In other words, the code above is the same as calling nested_belongs_to.
|
49
|
+
#
|
50
|
+
module BelongsToHelpers
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
# Parent is always true when belongs_to is called.
|
55
|
+
#
|
56
|
+
def parent?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Evaluate the parent given. This is used to nest parents in the
|
63
|
+
# association chain.
|
64
|
+
#
|
65
|
+
def evaluate_parent(parent_symbol, parent_config, chain = nil) #:nodoc:
|
66
|
+
instantiated_object = instance_variable_get("@#{parent_config[:instance_name]}")
|
67
|
+
return instantiated_object if instantiated_object
|
68
|
+
|
69
|
+
parent = if chain
|
70
|
+
chain.send(parent_config[:collection_name])
|
71
|
+
else
|
72
|
+
parent_config[:parent_class]
|
73
|
+
end
|
74
|
+
|
75
|
+
parent = parent.send(parent_config[:finder], params[parent_config[:param]])
|
76
|
+
|
77
|
+
instance_variable_set("@#{parent_config[:instance_name]}", parent)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Maps parents_symbols to build association chain. In this case, it
|
81
|
+
# simply return the parent_symbols, however on polymorphic belongs to,
|
82
|
+
# it has some customization.
|
83
|
+
#
|
84
|
+
def symbols_for_association_chain #:nodoc:
|
85
|
+
parents_symbols
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
module InheritedResources
|
2
|
+
module ClassMethods
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
# Used to overwrite the default assumptions InheritedResources do. Whenever
|
7
|
+
# this method is called, it should be on the top of your controller, since
|
8
|
+
# almost other methods depends on the values given to <<tt>>defaults</tt>.
|
9
|
+
#
|
10
|
+
# == Options
|
11
|
+
#
|
12
|
+
# * <tt>:resource_class</tt> - The resource class which by default is guessed
|
13
|
+
# by the controller name. Defaults to Project in
|
14
|
+
# ProjectsController.
|
15
|
+
#
|
16
|
+
# * <tt>:collection_name</tt> - The name of the collection instance variable which
|
17
|
+
# is set on the index action. Defaults to :projects in
|
18
|
+
# ProjectsController.
|
19
|
+
#
|
20
|
+
# * <tt>:instance_name</tt> - The name of the singular instance variable which
|
21
|
+
# is set on all actions besides index action. Defaults to
|
22
|
+
# :project in ProjectsController.
|
23
|
+
#
|
24
|
+
# * <tt>:route_collection_name</tt> - The name of the collection route. Defaults to :collection_name.
|
25
|
+
#
|
26
|
+
# * <tt>:route_instance_name</tt> - The name of the singular route. Defaults to :instance_name.
|
27
|
+
#
|
28
|
+
# * <tt>:route_prefix</tt> - The route prefix which is automically set in namespaced
|
29
|
+
# controllers. Default to :admin on Admin::ProjectsController.
|
30
|
+
#
|
31
|
+
# * <tt>:singleton</tt> - Tells if this controller is singleton or not.
|
32
|
+
#
|
33
|
+
def defaults(options)
|
34
|
+
raise ArgumentError, 'Class method :defaults expects a hash of options.' unless options.is_a? Hash
|
35
|
+
|
36
|
+
options.symbolize_keys!
|
37
|
+
options.assert_valid_keys(:resource_class, :collection_name, :instance_name,
|
38
|
+
:class_name, :route_prefix, :route_collection_name,
|
39
|
+
:route_instance_name, :singleton)
|
40
|
+
|
41
|
+
self.resource_class = options.delete(:resource_class) if options.key?(:resource_class)
|
42
|
+
self.resource_class = options.delete(:class_name).constantize if options.key?(:class_name)
|
43
|
+
|
44
|
+
acts_as_singleton! if options.delete(:singleton)
|
45
|
+
|
46
|
+
config = self.resources_configuration[:self]
|
47
|
+
config[:route_prefix] = options.delete(:route_prefix) if options.key?(:route_prefix)
|
48
|
+
|
49
|
+
options.each do |key, value|
|
50
|
+
config[key] = value.to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
create_resources_url_helpers!
|
54
|
+
end
|
55
|
+
|
56
|
+
# Defines wich actions to keep from the inherited controller.
|
57
|
+
# Syntax is borrowed from resource_controller.
|
58
|
+
#
|
59
|
+
# actions :index, :show, :edit
|
60
|
+
# actions :all, :except => :index
|
61
|
+
#
|
62
|
+
def actions(*actions_to_keep)
|
63
|
+
raise ArgumentError, 'Wrong number of arguments. You have to provide which actions you want to keep.' if actions_to_keep.empty?
|
64
|
+
|
65
|
+
options = actions_to_keep.extract_options!
|
66
|
+
actions_to_remove = Array(options[:except])
|
67
|
+
actions_to_remove += ACTIONS - actions_to_keep.map { |a| a.to_sym } unless actions_to_keep.first == :all
|
68
|
+
actions_to_remove.map! { |a| a.to_sym }.uniq!
|
69
|
+
(instance_methods.map { |m| m.to_sym } & actions_to_remove).each do |action|
|
70
|
+
undef_method action, "#{action}!"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Detects params from url and apply as scopes to your classes.
|
75
|
+
#
|
76
|
+
# Your model:
|
77
|
+
#
|
78
|
+
# class Graduation < ActiveRecord::Base
|
79
|
+
# named_scope :featured, :conditions => { :featured => true }
|
80
|
+
# named_scope :by_degree, proc {|degree| { :conditions => { :degree => degree } } }
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Your controller:
|
84
|
+
#
|
85
|
+
# class GraduationsController < InheritedResources::Base
|
86
|
+
# has_scope :featured, :boolean => true, :only => :index
|
87
|
+
# has_scope :by_degree, :only => :index
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# Then for each request:
|
91
|
+
#
|
92
|
+
# /graduations
|
93
|
+
# #=> acts like a normal request
|
94
|
+
#
|
95
|
+
# /graduations?featured=true
|
96
|
+
# #=> calls the named scope and bring featured graduations
|
97
|
+
#
|
98
|
+
# /graduations?featured=true&by_degree=phd
|
99
|
+
# #=> brings featured graduations with phd degree
|
100
|
+
#
|
101
|
+
# You can retrieve the current scopes in use with <tt>current_scopes</tt>
|
102
|
+
# method. In the last case, it would return: { :featured => "true", :by_degree => "phd" }
|
103
|
+
#
|
104
|
+
# == Options
|
105
|
+
#
|
106
|
+
# * <tt>:boolean</tt> - When set to true, call the scope only when the param is true or 1,
|
107
|
+
# and does not send the value as argument.
|
108
|
+
#
|
109
|
+
# * <tt>:only</tt> - In which actions the scope is applied. By default is :all.
|
110
|
+
#
|
111
|
+
# * <tt>:except</tt> - In which actions the scope is not applied. By default is :none.
|
112
|
+
#
|
113
|
+
# * <tt>:as</tt> - The key in the params hash expected to find the scope.
|
114
|
+
# Defaults to the scope name.
|
115
|
+
#
|
116
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
117
|
+
# if the scope should apply
|
118
|
+
#
|
119
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
120
|
+
# if the scope should NOT apply.
|
121
|
+
#
|
122
|
+
# * <tt>:default</tt> - Default value for the scope. Whenever supplied the scope
|
123
|
+
# is always called. This is useful to add easy pagination.
|
124
|
+
#
|
125
|
+
def has_scope(*scopes)
|
126
|
+
options = scopes.extract_options!
|
127
|
+
|
128
|
+
options.symbolize_keys!
|
129
|
+
options.assert_valid_keys(:boolean, :key, :only, :except,
|
130
|
+
:if, :unless, :default, :as)
|
131
|
+
|
132
|
+
if options[:key]
|
133
|
+
ActiveSupport::Deprecation.warn "has_scope :key is deprecated, use :as instead"
|
134
|
+
options[:as] ||= options[:key]
|
135
|
+
end
|
136
|
+
|
137
|
+
if self.scopes_configuration.empty?
|
138
|
+
include HasScopeHelpers
|
139
|
+
helper_method :current_scopes
|
140
|
+
end
|
141
|
+
|
142
|
+
scopes.each do |scope|
|
143
|
+
self.scopes_configuration[scope] ||= {}
|
144
|
+
self.scopes_configuration[scope][:as] = options[:as] || scope
|
145
|
+
self.scopes_configuration[scope][:only] = Array(options[:only])
|
146
|
+
self.scopes_configuration[scope][:except] = Array(options[:except])
|
147
|
+
|
148
|
+
[:if, :unless, :boolean, :default].each do |opt|
|
149
|
+
self.scopes_configuration[scope][opt] = options[opt] if options.key?(opt)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Defines that this controller belongs to another resource.
|
155
|
+
#
|
156
|
+
# belongs_to :projects
|
157
|
+
#
|
158
|
+
# == Options
|
159
|
+
#
|
160
|
+
# * <tt>:parent_class</tt> - Allows you to specify what is the parent class.
|
161
|
+
#
|
162
|
+
# belongs_to :project, :parent_class => AdminProject
|
163
|
+
#
|
164
|
+
# * <tt>:class_name</tt> - Also allows you to specify the parent class, but you should
|
165
|
+
# give a string. Added for ActiveRecord belongs to compatibility.
|
166
|
+
#
|
167
|
+
# * <tt>:instance_name</tt> - The instance variable name. By default is the name of the association.
|
168
|
+
#
|
169
|
+
# belongs_to :project, :instance_name => :my_project
|
170
|
+
#
|
171
|
+
# * <tt>:finder</tt> - Specifies which method should be called to instantiate the parent.
|
172
|
+
#
|
173
|
+
# belongs_to :project, :finder => :find_by_title!
|
174
|
+
#
|
175
|
+
# This will make your projects be instantiated as:
|
176
|
+
#
|
177
|
+
# Project.find_by_title!(params[:project_id])
|
178
|
+
#
|
179
|
+
# Instead of:
|
180
|
+
#
|
181
|
+
# Project.find(params[:project_id])
|
182
|
+
#
|
183
|
+
# * <tt>:param</tt> - Allows you to specify params key to retrieve the id.
|
184
|
+
# Default is :association_id, which in this case is :project_id.
|
185
|
+
#
|
186
|
+
# * <tt>:route_name</tt> - Allows you to specify what is the route name in your url
|
187
|
+
# helper. By default is association name.
|
188
|
+
#
|
189
|
+
# * <tt>:collection_name</tt> - Tell how to retrieve the next collection. Let's
|
190
|
+
# suppose you have Tasks which belongs to Projects
|
191
|
+
# which belongs to companies. This will do somewhere
|
192
|
+
# down the road:
|
193
|
+
#
|
194
|
+
# @company.projects
|
195
|
+
#
|
196
|
+
# But if you want to retrieve instead:
|
197
|
+
#
|
198
|
+
# @company.admin_projects
|
199
|
+
#
|
200
|
+
# You supply the collection name.
|
201
|
+
#
|
202
|
+
# * <tt>:polymorphic</tt> - Tell the association is polymorphic.
|
203
|
+
#
|
204
|
+
# * <tt>:singleton</tt> - Tell it's a singleton association.
|
205
|
+
#
|
206
|
+
# * <tt>:optional</tt> - Tell the association is optional (it's a special
|
207
|
+
# type of polymorphic association)
|
208
|
+
#
|
209
|
+
def belongs_to(*symbols, &block)
|
210
|
+
options = symbols.extract_options!
|
211
|
+
|
212
|
+
options.symbolize_keys!
|
213
|
+
options.assert_valid_keys(:class_name, :parent_class, :instance_name, :param,
|
214
|
+
:finder, :route_name, :collection_name, :singleton,
|
215
|
+
:polymorphic, :optional)
|
216
|
+
|
217
|
+
optional = options.delete(:optional)
|
218
|
+
singleton = options.delete(:singleton)
|
219
|
+
polymorphic = options.delete(:polymorphic)
|
220
|
+
finder = options.delete(:finder)
|
221
|
+
|
222
|
+
include BelongsToHelpers if self.parents_symbols.empty?
|
223
|
+
|
224
|
+
acts_as_singleton! if singleton
|
225
|
+
acts_as_polymorphic! if polymorphic || optional
|
226
|
+
|
227
|
+
raise ArgumentError, 'You have to give me at least one association name.' if symbols.empty?
|
228
|
+
raise ArgumentError, 'You cannot define multiple associations with options: #{options.keys.inspect} to belongs to.' unless symbols.size == 1 || options.empty?
|
229
|
+
|
230
|
+
symbols.each do |symbol|
|
231
|
+
symbol = symbol.to_sym
|
232
|
+
|
233
|
+
if polymorphic || optional
|
234
|
+
self.parents_symbols << :polymorphic unless self.parents_symbols.include?(:polymorphic)
|
235
|
+
self.resources_configuration[:polymorphic][:symbols] << symbol
|
236
|
+
self.resources_configuration[:polymorphic][:optional] ||= optional
|
237
|
+
else
|
238
|
+
self.parents_symbols << symbol
|
239
|
+
end
|
240
|
+
|
241
|
+
config = self.resources_configuration[symbol] = {}
|
242
|
+
config[:parent_class] = options.delete(:parent_class)
|
243
|
+
config[:parent_class] ||= (options.delete(:class_name) || symbol).to_s.pluralize.classify.constantize rescue nil
|
244
|
+
config[:collection_name] = options.delete(:collection_name) || symbol.to_s.pluralize.to_sym
|
245
|
+
config[:instance_name] = options.delete(:instance_name) || symbol
|
246
|
+
config[:param] = options.delete(:param) || :"#{symbol}_id"
|
247
|
+
config[:route_name] = options.delete(:route_name) || symbol
|
248
|
+
config[:finder] = finder || :find
|
249
|
+
end
|
250
|
+
|
251
|
+
if block_given?
|
252
|
+
class_eval(&block)
|
253
|
+
else
|
254
|
+
create_resources_url_helpers!
|
255
|
+
end
|
256
|
+
end
|
257
|
+
alias :nested_belongs_to :belongs_to
|
258
|
+
|
259
|
+
# A quick method to declare polymorphic belongs to.
|
260
|
+
#
|
261
|
+
def polymorphic_belongs_to(*symbols, &block)
|
262
|
+
options = symbols.extract_options!
|
263
|
+
options.merge!(:polymorphic => true)
|
264
|
+
belongs_to(*symbols << options, &block)
|
265
|
+
end
|
266
|
+
|
267
|
+
# A quick method to declare singleton belongs to.
|
268
|
+
#
|
269
|
+
def singleton_belongs_to(*symbols, &block)
|
270
|
+
options = symbols.extract_options!
|
271
|
+
options.merge!(:singleton => true)
|
272
|
+
belongs_to(*symbols << options, &block)
|
273
|
+
end
|
274
|
+
|
275
|
+
# A quick method to declare optional belongs to.
|
276
|
+
#
|
277
|
+
def optional_belongs_to(*symbols, &block)
|
278
|
+
options = symbols.extract_options!
|
279
|
+
options.merge!(:optional => true)
|
280
|
+
belongs_to(*symbols << options, &block)
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
def acts_as_singleton! #:nodoc:
|
286
|
+
unless self.resources_configuration[:self][:singleton]
|
287
|
+
self.resources_configuration[:self][:singleton] = true
|
288
|
+
include SingletonHelpers
|
289
|
+
actions :all, :except => :index
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def acts_as_polymorphic! #:nodoc:
|
294
|
+
unless self.parents_symbols.include?(:polymorphic)
|
295
|
+
include PolymorphicHelpers
|
296
|
+
helper_method :parent, :parent_type, :parent_class, :parent?
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Initialize resources class accessors and set their default values.
|
301
|
+
#
|
302
|
+
def initialize_resources_class_accessors! #:nodoc:
|
303
|
+
# Initialize resource class
|
304
|
+
self.resource_class = begin
|
305
|
+
self.controller_name.classify.constantize
|
306
|
+
rescue NameError
|
307
|
+
nil
|
308
|
+
end
|
309
|
+
|
310
|
+
# Initialize resources configuration hash
|
311
|
+
self.resources_configuration ||= {}
|
312
|
+
config = self.resources_configuration[:self] = {}
|
313
|
+
config[:collection_name] = self.controller_name.to_sym
|
314
|
+
config[:instance_name] = self.controller_name.singularize.to_sym
|
315
|
+
|
316
|
+
config[:route_collection_name] = config[:collection_name]
|
317
|
+
config[:route_instance_name] = config[:instance_name]
|
318
|
+
|
319
|
+
# Deal with namespaced controllers
|
320
|
+
namespaces = self.controller_path.split('/')[0..-2]
|
321
|
+
config[:route_prefix] = namespaces.join('_') unless namespaces.empty?
|
322
|
+
|
323
|
+
# Initialize polymorphic, singleton, scopes and belongs_to parameters
|
324
|
+
self.parents_symbols ||= []
|
325
|
+
self.scopes_configuration ||= {}
|
326
|
+
self.resources_configuration[:polymorphic] ||= { :symbols => [], :optional => false }
|
327
|
+
end
|
328
|
+
|
329
|
+
# Hook called on inheritance.
|
330
|
+
#
|
331
|
+
def inherited(base) #:nodoc:
|
332
|
+
super(base)
|
333
|
+
base.send :initialize_resources_class_accessors!
|
334
|
+
base.send :create_resources_url_helpers!
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module InheritedResources
|
2
|
+
# Allows controllers to write actions using a class method DSL.
|
3
|
+
#
|
4
|
+
# class MyController < InheritedResources::Base
|
5
|
+
# create! do |success, failure|
|
6
|
+
# success.html { render :text => "It works!" }
|
7
|
+
# end
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
module DSL
|
11
|
+
def self.included(base)
|
12
|
+
ACTIONS.each do |action|
|
13
|
+
base.class_eval <<-WRITTER
|
14
|
+
def self.#{action}!(options={}, &block)
|
15
|
+
define_method :__#{action}, &block
|
16
|
+
class_eval <<-ACTION
|
17
|
+
def #{action}
|
18
|
+
super(\#{options.inspect}, &method(:__#{action}))
|
19
|
+
end
|
20
|
+
ACTION
|
21
|
+
end
|
22
|
+
WRITTER
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|