josevalim-inherited_resources 0.3 → 0.4
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 +7 -0
- data/README +30 -1
- data/lib/inherited_resources/base.rb +1 -3
- data/lib/inherited_resources/belongs_to_helpers.rb +6 -30
- data/lib/inherited_resources/class_methods.rb +241 -4
- data/lib/inherited_resources/polymorphic_helpers.rb +47 -2
- data/lib/inherited_resources/url_helpers.rb +2 -1
- data/test/belongs_to_test.rb +66 -76
- data/test/class_methods_test.rb +111 -2
- data/test/optional_belongs_to_test.rb +190 -0
- data/test/polymorphic_test.rb +111 -0
- data/test/singleton_test.rb +83 -0
- data/test/url_helpers_test.rb +36 -4
- data/test/views/products/edit.html.erb +1 -0
- data/test/views/products/index.html.erb +1 -0
- data/test/views/products/new.html.erb +1 -0
- data/test/views/products/show.html.erb +1 -0
- metadata +10 -7
- data/lib/inherited_resources/belongs_to.rb +0 -227
- data/test/belongs_to_base_test.rb +0 -268
- data/test/polymorphic_base_test.rb +0 -282
- data/test/singleton_base_test.rb +0 -226
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Inherited Resources
|
2
2
|
License: MIT
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4
|
4
4
|
|
5
5
|
You can also read this README in pretty html at the GitHub project Wiki page:
|
6
6
|
|
@@ -347,6 +347,35 @@ When using polymorphic associations, you get some free helpers:
|
|
347
347
|
Polymorphic controller is another great idea by James Golick and he also uses
|
348
348
|
that on resource_controller.
|
349
349
|
|
350
|
+
Optional belongs to
|
351
|
+
-------------------
|
352
|
+
|
353
|
+
Let's take another break from Projects. Let's suppose that we are now building
|
354
|
+
a store, which sell products.
|
355
|
+
|
356
|
+
On the website, we can show all products, but also products scoped to
|
357
|
+
categories, brands, users, etc. In this case case, the association is optional,
|
358
|
+
and we deal with it in the following way:
|
359
|
+
|
360
|
+
class ProductsController < InheritedResources::Base
|
361
|
+
belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
|
362
|
+
end
|
363
|
+
|
364
|
+
This will handle all those urls properly:
|
365
|
+
|
366
|
+
/products/1
|
367
|
+
/categories/2/products/5
|
368
|
+
/brands/10/products/3
|
369
|
+
/user/13/products/11
|
370
|
+
|
371
|
+
This is treated as a special type of polymorphic associations, thus all helpers
|
372
|
+
are available. As you expect, when no parent is found, the helpers return:
|
373
|
+
|
374
|
+
parent? #=> false
|
375
|
+
parent_type #=> nil
|
376
|
+
parent_class #=> nil
|
377
|
+
parent #=> nil
|
378
|
+
|
350
379
|
Singletons
|
351
380
|
----------
|
352
381
|
|
@@ -161,7 +161,6 @@
|
|
161
161
|
# Let's require all needed files here. We are still on time to eager load
|
162
162
|
# everything on multithreaded environments.
|
163
163
|
require File.dirname(__FILE__) + '/base_helpers.rb'
|
164
|
-
require File.dirname(__FILE__) + '/belongs_to.rb'
|
165
164
|
require File.dirname(__FILE__) + '/belongs_to_helpers.rb'
|
166
165
|
require File.dirname(__FILE__) + '/class_methods.rb'
|
167
166
|
require File.dirname(__FILE__) + '/dumb_responder.rb'
|
@@ -176,12 +175,11 @@ module InheritedResources
|
|
176
175
|
unloadable
|
177
176
|
|
178
177
|
include InheritedResources::BaseHelpers
|
179
|
-
extend InheritedResources::BelongsTo
|
180
178
|
extend InheritedResources::ClassMethods
|
181
179
|
|
182
180
|
helper_method :collection_url, :collection_path, :resource_url, :resource_path,
|
183
181
|
:new_resource_url, :new_resource_path, :edit_resource_url, :edit_resource_path,
|
184
|
-
:resource, :collection, :resource_class
|
182
|
+
:resource, :collection, :resource_class, :parent?
|
185
183
|
|
186
184
|
def self.inherited(base)
|
187
185
|
base.class_eval do
|
@@ -4,10 +4,6 @@ module InheritedResources #:nodoc:
|
|
4
4
|
# Private helpers, you probably don't have to worry with them.
|
5
5
|
private
|
6
6
|
|
7
|
-
# Overwrites the parent? method defined in base_helpers.rb.
|
8
|
-
# This one always returns true since it's added when associations
|
9
|
-
# are defined.
|
10
|
-
#
|
11
7
|
def parent?
|
12
8
|
true
|
13
9
|
end
|
@@ -33,14 +29,13 @@ module InheritedResources #:nodoc:
|
|
33
29
|
# parents chain and returns the scoped association.
|
34
30
|
#
|
35
31
|
def end_of_association_chain
|
36
|
-
return resource_class unless parent?
|
37
|
-
|
38
32
|
chain = symbols_for_chain.inject(begin_of_association_chain) do |chain, symbol|
|
39
33
|
evaluate_parent(resources_configuration[symbol], chain)
|
40
34
|
end
|
41
35
|
|
42
|
-
|
36
|
+
return resource_class unless chain
|
43
37
|
|
38
|
+
chain = chain.send(method_for_association_chain) if method_for_association_chain
|
44
39
|
return chain
|
45
40
|
end
|
46
41
|
|
@@ -58,31 +53,12 @@ module InheritedResources #:nodoc:
|
|
58
53
|
singleton ? nil : resource_collection_name
|
59
54
|
end
|
60
55
|
|
61
|
-
# Maps parents_symbols to build association chain.
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# params keys to see which polymorphic parent matches the given params.
|
56
|
+
# Maps parents_symbols to build association chain. In this case, it
|
57
|
+
# simply return the parent_symbols, however on polymorphic belongs to,
|
58
|
+
# it has some customization to deal with properly.
|
65
59
|
#
|
66
60
|
def symbols_for_chain
|
67
|
-
parents_symbols
|
68
|
-
if symbol == :polymorphic
|
69
|
-
params_keys = params.keys
|
70
|
-
|
71
|
-
key = polymorphic_symbols.find do |poly|
|
72
|
-
params_keys.include? resources_configuration[poly][:param].to_s
|
73
|
-
end
|
74
|
-
|
75
|
-
raise ScriptError, "Could not find param for polymorphic association.
|
76
|
-
The request params keys are #{params.keys.inspect}
|
77
|
-
and the polymorphic associations are
|
78
|
-
#{polymorphic_symbols.inspect}." if key.nil?
|
79
|
-
|
80
|
-
instance_variable_set('@parent_type', key.to_sym)
|
81
|
-
else
|
82
|
-
symbol
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
61
|
+
parents_symbols
|
86
62
|
end
|
87
63
|
|
88
64
|
end
|
@@ -1,3 +1,186 @@
|
|
1
|
+
# = belongs to
|
2
|
+
#
|
3
|
+
# This allows you to specify to belongs_to in your controller. You might use
|
4
|
+
# this when you are having nested resources in your routes:
|
5
|
+
#
|
6
|
+
# class TasksController < InheritedResources::Base
|
7
|
+
# belongs_to :project
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# This will do all magic assuming some defaults. It assumes that your URL to
|
11
|
+
# access those tasks are:
|
12
|
+
#
|
13
|
+
# /projects/:project_id/tasks
|
14
|
+
#
|
15
|
+
# But all defaults are configurable. The options are:
|
16
|
+
#
|
17
|
+
# * :parent_class => Allows you to specify what is the parent class.
|
18
|
+
#
|
19
|
+
# belongs_to :project, :parent_class => AdminProject
|
20
|
+
#
|
21
|
+
# * :class_name => Also allows you to specify the parent class, but you should
|
22
|
+
# give a string. Added for ActiveRecord belongs to compatibility.
|
23
|
+
#
|
24
|
+
# * :instance_name => How this object will appear in your views. In this case
|
25
|
+
# the default is @project. Overwrite it with a symbol.
|
26
|
+
#
|
27
|
+
# belongs_to :project, :instance_name => :my_project
|
28
|
+
#
|
29
|
+
# * :finder => Specifies which method should be called to instantiate the
|
30
|
+
# parent. Let's suppose you are using slugs ("this-is-project-title") in URLs
|
31
|
+
# so your tasks url would be: "projects/this-is-project-title/tasks". Then you
|
32
|
+
# should do this in your TasksController:
|
33
|
+
#
|
34
|
+
# belongs_to :project, :finder => :find_by_title!
|
35
|
+
#
|
36
|
+
# This will make your projects be instantiated as:
|
37
|
+
#
|
38
|
+
# Project.find_by_title!(params[:project_id])
|
39
|
+
#
|
40
|
+
# Instead of:
|
41
|
+
#
|
42
|
+
# Project.find(params[:project_id])
|
43
|
+
#
|
44
|
+
# * param => Allows you to specify params key used to instantiate the parent.
|
45
|
+
# Default is :parent_id, which in this case is :project_id.
|
46
|
+
#
|
47
|
+
# * route_name => Allows you to specify what is the route name in your url
|
48
|
+
# helper. By default is 'project'. But if your url helper should be
|
49
|
+
# "admin_project_task_url" instead of "project_task_url", just do:
|
50
|
+
#
|
51
|
+
# belongs_to :project, :route_name => "admin_project"
|
52
|
+
#
|
53
|
+
# = nested_belongs_to
|
54
|
+
#
|
55
|
+
# If for some reason you need to nested more than two resources, you can do:
|
56
|
+
#
|
57
|
+
# class TasksController
|
58
|
+
# belongs_to :company, :project
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# ATTENTION! This DOES NOT mean polymorphic associations as in resource_controller.
|
62
|
+
# Polymorphic associations are not supported yet.
|
63
|
+
#
|
64
|
+
# It means that companies have many projects which have many tasks. You URL
|
65
|
+
# should be:
|
66
|
+
#
|
67
|
+
# /companies/:company_id/projects/:project_id/tasks/:id
|
68
|
+
#
|
69
|
+
# Everything will be handled for you again. And all defaults will describe above
|
70
|
+
# will be assumed. But if you have to change the defaults. You will have to
|
71
|
+
# specify one association by one:
|
72
|
+
#
|
73
|
+
# class TasksController
|
74
|
+
# belongs_to :company, :finder => :find_by_name!, :param => :company_name
|
75
|
+
# belongs_to :project
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# belongs_to is aliased as nested_belongs_to, so this provides a nicer syntax:
|
79
|
+
#
|
80
|
+
# class TasksController
|
81
|
+
# nested_belongs_to :company, :finder => :find_by_name!, :param => :company_name
|
82
|
+
# nested_belongs_to :project
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# In this case the association chain would be:
|
86
|
+
#
|
87
|
+
# Company.find_by_name!(params[:company_name]).projects.find(params[:project_id]).tasks.find(:all)
|
88
|
+
#
|
89
|
+
# When you are using nested resources, you have one more option to config.
|
90
|
+
# Let's suppose that to get all projects from a company, you have to do:
|
91
|
+
#
|
92
|
+
# Company.admin_projects
|
93
|
+
#
|
94
|
+
# Instead of:
|
95
|
+
#
|
96
|
+
# Company.projects
|
97
|
+
#
|
98
|
+
# In this case, you can set the collection_name in belongs_to:
|
99
|
+
#
|
100
|
+
# nested_belongs_to :project, :collection_name => 'admin_projects'
|
101
|
+
#
|
102
|
+
# = polymorphic associations
|
103
|
+
#
|
104
|
+
# In some cases you have a resource that belongs to two different resources
|
105
|
+
# but not at the same time. For example, let's suppose you have File, Message
|
106
|
+
# and Task as resources and they are all commentable.
|
107
|
+
#
|
108
|
+
# Polymorphic associations allows you to create just one controller that will
|
109
|
+
# deal with each case.
|
110
|
+
#
|
111
|
+
# class Comment < InheritedResources::Base
|
112
|
+
# belongs_to :file, :message, :task, :polymorphic => true
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# Your routes should be something like:
|
116
|
+
#
|
117
|
+
# m.resources :files, :has_many => :comments #=> /files/13/comments
|
118
|
+
# m.resources :tasks, :has_many => :comments #=> /tasks/17/comments
|
119
|
+
# m.resources :messages, :has_many => :comments #=> /messages/11/comments
|
120
|
+
#
|
121
|
+
# When using polymorphic associations, you get some free helpers:
|
122
|
+
#
|
123
|
+
# parent? #=> true
|
124
|
+
# parent_type #=> :task
|
125
|
+
# parent_class #=> Task
|
126
|
+
# parent #=> @task
|
127
|
+
#
|
128
|
+
# This polymorphic controllers thing is a great idea by James Golick and he
|
129
|
+
# built it in resource_controller. Here is just a re-implementation.
|
130
|
+
#
|
131
|
+
# = optional polymorphic associations
|
132
|
+
#
|
133
|
+
# Let's take another break from ProjectsController. Let's suppose we are
|
134
|
+
# building a store, which sell products.
|
135
|
+
#
|
136
|
+
# On the website, we can show all products, but also products scoped to
|
137
|
+
# categories, brands, users. In this case case, the association is optional, and
|
138
|
+
# we deal with it in the following way:
|
139
|
+
#
|
140
|
+
# class ProductsController < InheritedResources::Base
|
141
|
+
# belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# This will handle all those urls properly:
|
145
|
+
#
|
146
|
+
# /products/1
|
147
|
+
# /categories/2/products/5
|
148
|
+
# /brands/10/products/3
|
149
|
+
# /user/13/products/11
|
150
|
+
#
|
151
|
+
# = nested polymorphic associations
|
152
|
+
#
|
153
|
+
# You can have polymorphic associations with nested resources. Let's suppose
|
154
|
+
# that our File, Task and Message resources in the previous example belongs to
|
155
|
+
# a project.
|
156
|
+
#
|
157
|
+
# This way we can have:
|
158
|
+
#
|
159
|
+
# class CommentsController < InheritedResources::Base
|
160
|
+
# belongs_to :project {
|
161
|
+
# belongs_to :file, :message, :task, :polymorphic => true
|
162
|
+
# }
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# Or:
|
166
|
+
#
|
167
|
+
# class CommentsController < InheritedResources::Base
|
168
|
+
# nested_belongs_to :project
|
169
|
+
# nested_belongs_to :file, :message, :task, :polymorphic => true
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# Choose the syntax that makes more sense to you. :)
|
173
|
+
#
|
174
|
+
# Finally your routes should be something like:
|
175
|
+
#
|
176
|
+
# map.resources :projects do |m|
|
177
|
+
# m.resources :files, :has_many => :comments #=> /projects/1/files/13/comments
|
178
|
+
# m.resources :tasks, :has_many => :comments #=> /projects/1/tasks/17/comments
|
179
|
+
# m.resources :messages, :has_many => :comments #=> /projects/1/messages/11/comments
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# The helpers work in the same way as above.
|
183
|
+
#
|
1
184
|
# = singleton
|
2
185
|
#
|
3
186
|
# Singletons are usually used in associations which are related through has_one
|
@@ -33,7 +216,7 @@
|
|
33
216
|
# When you have a singleton controller, the action index is removed.
|
34
217
|
#
|
35
218
|
module InheritedResources #:nodoc:
|
36
|
-
RESOURCES_CLASS_ACCESSORS = [ :resource_class, :resources_configuration, :parents_symbols, :singleton
|
219
|
+
RESOURCES_CLASS_ACCESSORS = [ :resource_class, :resources_configuration, :parents_symbols, :singleton ] unless self.const_defined? "RESOURCES_CLASS_ACCESSORS"
|
37
220
|
|
38
221
|
module ClassMethods #:nodoc:
|
39
222
|
|
@@ -94,6 +277,60 @@ module InheritedResources #:nodoc:
|
|
94
277
|
end
|
95
278
|
end
|
96
279
|
|
280
|
+
# Defines that this controller belongs to another resource.
|
281
|
+
#
|
282
|
+
# belongs_to :projects
|
283
|
+
#
|
284
|
+
def belongs_to(*symbols, &block)
|
285
|
+
options = symbols.extract_options!
|
286
|
+
|
287
|
+
options.symbolize_keys!
|
288
|
+
options.assert_valid_keys(:class_name, :parent_class, :instance_name, :param, :finder, :route_name, :collection_name, :singleton, :polymorphic, :optional)
|
289
|
+
|
290
|
+
optional = options.delete(:optional)
|
291
|
+
singleton = options.delete(:singleton)
|
292
|
+
polymorphic = options.delete(:polymorphic)
|
293
|
+
|
294
|
+
# Add BelongsToHelpers if we haven't yet.
|
295
|
+
include BelongsToHelpers if self.parents_symbols.empty?
|
296
|
+
|
297
|
+
acts_as_singleton! if singleton
|
298
|
+
acts_as_polymorphic! if polymorphic || optional
|
299
|
+
|
300
|
+
raise ArgumentError, 'You have to give me at least one association name.' if symbols.empty?
|
301
|
+
raise ArgumentError, 'You cannot define multiple associations with the options: #{options.keys.inspect}.' unless symbols.size == 1 || options.empty?
|
302
|
+
|
303
|
+
# Set configuration default values
|
304
|
+
symbols.each do |symbol|
|
305
|
+
symbol = symbol.to_sym
|
306
|
+
|
307
|
+
if polymorphic || optional
|
308
|
+
self.parents_symbols << :polymorphic unless self.parents_symbols.include? :polymorphic
|
309
|
+
self.resources_configuration[:polymorphic][:symbols] << symbol
|
310
|
+
self.resources_configuration[:polymorphic][:optional] ||= optional
|
311
|
+
else
|
312
|
+
self.parents_symbols << symbol
|
313
|
+
end
|
314
|
+
|
315
|
+
config = self.resources_configuration[symbol] = {}
|
316
|
+
config[:parent_class] = options.delete(:parent_class)
|
317
|
+
config[:parent_class] ||= (options.delete(:class_name) || symbol).to_s.classify.constantize rescue nil
|
318
|
+
config[:collection_name] = (options.delete(:collection_name) || symbol.to_s.pluralize).to_sym
|
319
|
+
config[:instance_name] = (options.delete(:instance_name) || symbol).to_sym
|
320
|
+
config[:param] = (options.delete(:param) || "#{symbol}_id").to_sym
|
321
|
+
config[:finder] = (options.delete(:finder) || :find).to_sym
|
322
|
+
config[:route_name] = (options.delete(:route_name) || symbol).to_s
|
323
|
+
end
|
324
|
+
|
325
|
+
# Regenerate url helpers unless block is given
|
326
|
+
if block_given?
|
327
|
+
class_eval(&block)
|
328
|
+
else
|
329
|
+
InheritedResources::UrlHelpers.create_resources_url_helpers!(self)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
alias :nested_belongs_to :belongs_to
|
333
|
+
|
97
334
|
private
|
98
335
|
|
99
336
|
# Defines this controller as singleton.
|
@@ -111,9 +348,9 @@ module InheritedResources #:nodoc:
|
|
111
348
|
# Do not call this method on your own.
|
112
349
|
#
|
113
350
|
def acts_as_polymorphic!
|
114
|
-
|
351
|
+
unless self.parents_symbols.include? :polymorphic
|
115
352
|
include PolymorphicHelpers
|
116
|
-
helper_method :parent
|
353
|
+
helper_method :parent, :parent_type, :parent_class
|
117
354
|
end
|
118
355
|
end
|
119
356
|
|
@@ -146,7 +383,7 @@ module InheritedResources #:nodoc:
|
|
146
383
|
# Initialize polymorphic, singleton and belongs_to parameters
|
147
384
|
base.singleton = false
|
148
385
|
base.parents_symbols = []
|
149
|
-
base.
|
386
|
+
base.resources_configuration[:polymorphic] = { :symbols => [], :optional => false }
|
150
387
|
|
151
388
|
# Create helpers
|
152
389
|
InheritedResources::UrlHelpers.create_resources_url_helpers!(base)
|
@@ -8,11 +8,56 @@ module InheritedResources #:nodoc:
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def parent_class
|
11
|
-
parent.class
|
11
|
+
parent.class if @parent_type
|
12
12
|
end
|
13
13
|
|
14
14
|
def parent
|
15
|
-
instance_variable_get("@#{@parent_type}")
|
15
|
+
instance_variable_get("@#{@parent_type}") if @parent_type
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def parent?
|
21
|
+
if resources_configuration[:polymorphic][:optional]
|
22
|
+
!@parent_type.nil?
|
23
|
+
else
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Maps parents_symbols to build association chain.
|
29
|
+
#
|
30
|
+
# If the parents_symbols find :polymorphic, it goes through the
|
31
|
+
# params keys to see which polymorphic parent matches the given params.
|
32
|
+
#
|
33
|
+
# When optional is given, it does not raise errors if the polymorphic
|
34
|
+
# params are missing.
|
35
|
+
#
|
36
|
+
def symbols_for_chain
|
37
|
+
polymorphic_config = resources_configuration[:polymorphic]
|
38
|
+
|
39
|
+
parents_symbols.map do |symbol|
|
40
|
+
if symbol == :polymorphic
|
41
|
+
params_keys = params.keys
|
42
|
+
|
43
|
+
key = polymorphic_config[:symbols].find do |poly|
|
44
|
+
params_keys.include? resources_configuration[poly][:param].to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
if key.nil?
|
48
|
+
raise ScriptError, "Could not find param for polymorphic association.
|
49
|
+
The request params keys are #{params.keys.inspect}
|
50
|
+
and the polymorphic associations are
|
51
|
+
#{polymorphic_symbols.inspect}." unless polymorphic_config[:optional]
|
52
|
+
|
53
|
+
nil
|
54
|
+
else
|
55
|
+
@parent_type = key.to_sym
|
56
|
+
end
|
57
|
+
else
|
58
|
+
symbol
|
59
|
+
end
|
60
|
+
end.compact
|
16
61
|
end
|
17
62
|
|
18
63
|
end
|