josevalim-inherited_resources 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +9 -0
- data/README +132 -105
- data/lib/inherited_resources/base.rb +15 -189
- data/lib/inherited_resources/base_helpers.rb +75 -39
- data/lib/inherited_resources/belongs_to_helpers.rb +62 -40
- data/lib/inherited_resources/class_methods.rb +268 -272
- data/lib/inherited_resources/dumb_responder.rb +7 -6
- data/lib/inherited_resources/has_scope_helpers.rb +65 -0
- data/lib/inherited_resources/polymorphic_helpers.rb +96 -5
- data/lib/inherited_resources/respond_to.rb +7 -7
- data/lib/inherited_resources/singleton_helpers.rb +48 -6
- data/lib/inherited_resources/url_helpers.rb +37 -36
- data/lib/inherited_resources.rb +4 -2
- data/test/base_helpers_test.rb +3 -92
- data/test/class_methods_test.rb +116 -75
- data/test/customized_belongs_to_test.rb +76 -0
- data/test/defaults_test.rb +2 -1
- data/test/flash_test.rb +83 -0
- data/test/has_scope_test.rb +171 -0
- data/test/test_helper.rb +4 -8
- data/test/url_helpers_test.rb +3 -3
- data/test/views/branches/edit.html.erb +1 -0
- data/test/views/branches/index.html.erb +1 -0
- data/test/views/branches/new.html.erb +1 -0
- data/test/views/branches/show.html.erb +1 -0
- data/test/views/pets/index.html.erb +1 -0
- data/test/views/pets/new.html.erb +1 -0
- data/test/views/pets/show.html.erb +1 -0
- data/test/views/trees/edit.html.erb +1 -0
- data/test/views/trees/index.html.erb +1 -0
- data/test/views/trees/new.html.erb +1 -0
- data/test/views/trees/show.html.erb +1 -0
- metadata +17 -2
@@ -1,250 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
# "myproject_task_url" instead of "project_task_url", just do:
|
50
|
-
#
|
51
|
-
# belongs_to :project, :route_name => "myproject"
|
52
|
-
#
|
53
|
-
# But if you want to use namespaced routes, you can do:
|
54
|
-
#
|
55
|
-
# defaults :route_prefix => :admin
|
56
|
-
#
|
57
|
-
# That would generate "admin_project_task_url".
|
58
|
-
#
|
59
|
-
# = nested_belongs_to
|
60
|
-
#
|
61
|
-
# If for some reason you need to nested more than two resources, you can do:
|
62
|
-
#
|
63
|
-
# class TasksController
|
64
|
-
# belongs_to :company, :project
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# ATTENTION! This DOES NOT mean polymorphic associations as in resource_controller.
|
68
|
-
# Polymorphic associations are not supported yet.
|
69
|
-
#
|
70
|
-
# It means that companies have many projects which have many tasks. You URL
|
71
|
-
# should be:
|
72
|
-
#
|
73
|
-
# /companies/:company_id/projects/:project_id/tasks/:id
|
74
|
-
#
|
75
|
-
# Everything will be handled for you again. And all defaults will describe above
|
76
|
-
# will be assumed. But if you have to change the defaults. You will have to
|
77
|
-
# specify one association by one:
|
78
|
-
#
|
79
|
-
# class TasksController
|
80
|
-
# belongs_to :company, :finder => :find_by_name!, :param => :company_name
|
81
|
-
# belongs_to :project
|
82
|
-
# end
|
83
|
-
#
|
84
|
-
# belongs_to is aliased as nested_belongs_to, so this provides a nicer syntax:
|
85
|
-
#
|
86
|
-
# class TasksController
|
87
|
-
# nested_belongs_to :company, :finder => :find_by_name!, :param => :company_name
|
88
|
-
# nested_belongs_to :project
|
89
|
-
# end
|
90
|
-
#
|
91
|
-
# In this case the association chain would be:
|
92
|
-
#
|
93
|
-
# Company.find_by_name!(params[:company_name]).projects.find(params[:project_id]).tasks.find(:all)
|
94
|
-
#
|
95
|
-
# When you are using nested resources, you have one more option to config.
|
96
|
-
# Let's suppose that to get all projects from a company, you have to do:
|
97
|
-
#
|
98
|
-
# Company.admin_projects
|
99
|
-
#
|
100
|
-
# Instead of:
|
101
|
-
#
|
102
|
-
# Company.projects
|
103
|
-
#
|
104
|
-
# In this case, you can set the collection_name in belongs_to:
|
105
|
-
#
|
106
|
-
# nested_belongs_to :project, :collection_name => 'admin_projects'
|
107
|
-
#
|
108
|
-
# = polymorphic associations
|
109
|
-
#
|
110
|
-
# In some cases you have a resource that belongs to two different resources
|
111
|
-
# but not at the same time. For example, let's suppose you have File, Message
|
112
|
-
# and Task as resources and they are all commentable.
|
113
|
-
#
|
114
|
-
# Polymorphic associations allows you to create just one controller that will
|
115
|
-
# deal with each case.
|
116
|
-
#
|
117
|
-
# class Comment < InheritedResources::Base
|
118
|
-
# belongs_to :file, :message, :task, :polymorphic => true
|
119
|
-
# end
|
120
|
-
#
|
121
|
-
# Your routes should be something like:
|
122
|
-
#
|
123
|
-
# m.resources :files, :has_many => :comments #=> /files/13/comments
|
124
|
-
# m.resources :tasks, :has_many => :comments #=> /tasks/17/comments
|
125
|
-
# m.resources :messages, :has_many => :comments #=> /messages/11/comments
|
126
|
-
#
|
127
|
-
# When using polymorphic associations, you get some free helpers:
|
128
|
-
#
|
129
|
-
# parent? #=> true
|
130
|
-
# parent_type #=> :task
|
131
|
-
# parent_class #=> Task
|
132
|
-
# parent #=> @task
|
133
|
-
#
|
134
|
-
# This polymorphic controllers thing is a great idea by James Golick and he
|
135
|
-
# built it in resource_controller. Here is just a re-implementation.
|
136
|
-
#
|
137
|
-
# = optional polymorphic associations
|
138
|
-
#
|
139
|
-
# Let's take another break from ProjectsController. Let's suppose we are
|
140
|
-
# building a store, which sell products.
|
141
|
-
#
|
142
|
-
# On the website, we can show all products, but also products scoped to
|
143
|
-
# categories, brands, users. In this case case, the association is optional, and
|
144
|
-
# we deal with it in the following way:
|
145
|
-
#
|
146
|
-
# class ProductsController < InheritedResources::Base
|
147
|
-
# belongs_to :category, :brand, :user, :polymorphic => true, :optional => true
|
148
|
-
# end
|
149
|
-
#
|
150
|
-
# This will handle all those urls properly:
|
151
|
-
#
|
152
|
-
# /products/1
|
153
|
-
# /categories/2/products/5
|
154
|
-
# /brands/10/products/3
|
155
|
-
# /user/13/products/11
|
156
|
-
#
|
157
|
-
# = nested polymorphic associations
|
158
|
-
#
|
159
|
-
# You can have polymorphic associations with nested resources. Let's suppose
|
160
|
-
# that our File, Task and Message resources in the previous example belongs to
|
161
|
-
# a project.
|
162
|
-
#
|
163
|
-
# This way we can have:
|
164
|
-
#
|
165
|
-
# class CommentsController < InheritedResources::Base
|
166
|
-
# belongs_to :project {
|
167
|
-
# belongs_to :file, :message, :task, :polymorphic => true
|
168
|
-
# }
|
169
|
-
# end
|
170
|
-
#
|
171
|
-
# Or:
|
172
|
-
#
|
173
|
-
# class CommentsController < InheritedResources::Base
|
174
|
-
# nested_belongs_to :project
|
175
|
-
# nested_belongs_to :file, :message, :task, :polymorphic => true
|
176
|
-
# end
|
177
|
-
#
|
178
|
-
# Choose the syntax that makes more sense to you. :)
|
179
|
-
#
|
180
|
-
# Finally your routes should be something like:
|
181
|
-
#
|
182
|
-
# map.resources :projects do |m|
|
183
|
-
# m.resources :files, :has_many => :comments #=> /projects/1/files/13/comments
|
184
|
-
# m.resources :tasks, :has_many => :comments #=> /projects/1/tasks/17/comments
|
185
|
-
# m.resources :messages, :has_many => :comments #=> /projects/1/messages/11/comments
|
186
|
-
# end
|
187
|
-
#
|
188
|
-
# The helpers work in the same way as above.
|
189
|
-
#
|
190
|
-
# = singleton
|
191
|
-
#
|
192
|
-
# Singletons are usually used in associations which are related through has_one
|
193
|
-
# and belongs_to. You declare those associations like this:
|
194
|
-
#
|
195
|
-
# class ManagersController < InheritedResources::Base
|
196
|
-
# belongs_to :project, :singleton => true
|
197
|
-
# end
|
198
|
-
#
|
199
|
-
# But in some cases, like an AccountsController, you have a singleton object
|
200
|
-
# that is not necessarily associated with another:
|
201
|
-
#
|
202
|
-
# class AccountsController < InheritedResources::Base
|
203
|
-
# defaults :singleton => true
|
204
|
-
# end
|
205
|
-
#
|
206
|
-
# Besides that, you should overwrite the methods :resource and :build_resource
|
207
|
-
# to make it work properly:
|
208
|
-
#
|
209
|
-
# class AccountsController < InheritedResources::Base
|
210
|
-
# defaults :singleton => true
|
211
|
-
#
|
212
|
-
# protected
|
213
|
-
# def resource
|
214
|
-
# @current_user.account
|
215
|
-
# end
|
216
|
-
#
|
217
|
-
# def build_resource(attributes = {})
|
218
|
-
# Account.new(attributes)
|
219
|
-
# end
|
220
|
-
# end
|
221
|
-
#
|
222
|
-
# When you have a singleton controller, the action index is removed.
|
223
|
-
#
|
224
|
-
module InheritedResources #:nodoc:
|
225
|
-
RESOURCES_CLASS_ACCESSORS = [ :resource_class, :resources_configuration, :parents_symbols, :singleton ] unless self.const_defined? "RESOURCES_CLASS_ACCESSORS"
|
226
|
-
|
227
|
-
module ClassMethods #:nodoc:
|
1
|
+
module InheritedResources
|
2
|
+
RESOURCES_CLASS_ACCESSORS = [ :resource_class, :resources_configuration,
|
3
|
+
:parents_symbols, :scopes_configuration ] unless self.const_defined?(:RESOURCES_CLASS_ACCESSORS)
|
4
|
+
|
5
|
+
module ClassMethods
|
228
6
|
|
229
7
|
protected
|
230
8
|
|
231
|
-
#
|
232
|
-
#
|
9
|
+
# Used to overwrite the default assumptions InheritedResources do. Whenever
|
10
|
+
# this method is called, it should be on the top of your controller, since
|
11
|
+
# almost other methods depends on the values given to <<tt>>defaults</tt>.
|
233
12
|
#
|
234
|
-
#
|
235
|
-
# This is useful, for example, in an accounts controller, where the object
|
236
|
-
# is an User but controller and routes are accounts.
|
13
|
+
# == Options
|
237
14
|
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
15
|
+
# * <tt>:resource_class</tt> - The resource class which by default is guessed
|
16
|
+
# by the controller name. Defaults to Project in
|
17
|
+
# ProjectsController.
|
18
|
+
#
|
19
|
+
# * <tt>:collection_name</tt> - The name of the collection instance variable which
|
20
|
+
# is set on the index action. Defaults to :projects in
|
21
|
+
# ProjectsController.
|
22
|
+
#
|
23
|
+
# * <tt>:instance_name</tt> - The name of the singular instance variable which
|
24
|
+
# is set on all actions besides index action. Defaults to
|
25
|
+
# :project in ProjectsController.
|
242
26
|
#
|
243
|
-
#
|
244
|
-
# :route_collection_name helpers.
|
27
|
+
# * <tt>:route_collection_name</tt> - The name of the collection route. Defaults to :collection_name.
|
245
28
|
#
|
246
|
-
#
|
247
|
-
#
|
29
|
+
# * <tt>:route_instance_name</tt> - The name of the singular route. Defaults to :instance_name.
|
30
|
+
#
|
31
|
+
# * <tt>:route_prefix</tt> - The route prefix which is automically set in namespaced
|
32
|
+
# controllers. Default to :admin on Admin::ProjectsController.
|
33
|
+
#
|
34
|
+
# * <tt>:singleton</tt> - Tells if this controller is singleton or not.
|
248
35
|
#
|
249
36
|
def defaults(options)
|
250
37
|
raise ArgumentError, 'Class method :defaults expects a hash of options.' unless options.is_a? Hash
|
@@ -287,16 +74,194 @@ module InheritedResources #:nodoc:
|
|
287
74
|
actions_to_remove += RESOURCES_ACTIONS.map{|a| a.to_s } - actions_to_keep unless actions_to_keep.first == 'all'
|
288
75
|
actions_to_remove.uniq!
|
289
76
|
|
290
|
-
# Undefine actions that we don't want
|
291
77
|
(instance_methods & actions_to_remove).each do |action|
|
292
78
|
undef_method action, "#{action}!"
|
293
79
|
end
|
294
80
|
end
|
295
81
|
|
82
|
+
# Detects params from url and apply as scopes to your classes.
|
83
|
+
#
|
84
|
+
# Your model:
|
85
|
+
#
|
86
|
+
# class Graduation < ActiveRecord::Base
|
87
|
+
# named_scope :featured, :conditions => { :featured => true }
|
88
|
+
# named_scope :by_degree, proc {|degree| { :conditions => { :degree => degree } } }
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# Your controller:
|
92
|
+
#
|
93
|
+
# class GraduationsController < InheritedResources::Base
|
94
|
+
# has_scope :featured, :boolean => true, :only => :index
|
95
|
+
# has_scope :by_degree, :only => :index
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# Then for each request:
|
99
|
+
#
|
100
|
+
# /graduations
|
101
|
+
# #=> acts like a normal request
|
102
|
+
#
|
103
|
+
# /graduations?featured=true
|
104
|
+
# #=> calls the named scope and bring featured graduations
|
105
|
+
#
|
106
|
+
# /graduations?featured=true&by_degree=phd
|
107
|
+
# #=> brings featured graduations with phd degree
|
108
|
+
#
|
109
|
+
# You can also specify the target of the scope. Let's suppose that a
|
110
|
+
# Graduation has many students:
|
111
|
+
#
|
112
|
+
# class StudentsController < InheritedResources::Base
|
113
|
+
# belongs_to :graduation
|
114
|
+
#
|
115
|
+
# has_scope :featured, :on => :graduation, :boolean => true, :only => :index
|
116
|
+
# has_scope :by_degree, :on => :graduation, :only => :index
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# You can also do this in a block:
|
120
|
+
#
|
121
|
+
# class StudentsController < InheritedResources::Base
|
122
|
+
# belongs_to :graduation do
|
123
|
+
# has_scope :featured, :boolean => true, :only => :index
|
124
|
+
# has_scope :by_degree, :only => :index
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# Or even better:
|
129
|
+
#
|
130
|
+
# class StudentsController < InheritedResources::Base
|
131
|
+
# belongs_to :graduation do
|
132
|
+
# load_scopes_from GraduationsController
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# Another feature is that you can retrive the current scopes in use with
|
137
|
+
# the method <tt>current_scopes</tt> that returns a hash.
|
138
|
+
#
|
139
|
+
# == Options
|
140
|
+
#
|
141
|
+
# * <tt>:on</tt> - In each resource the scope is applied to. Defaults to the resource class.
|
142
|
+
#
|
143
|
+
# * <tt>:boolean</tt> - When set to true, call the scope only when the params is true or 1,
|
144
|
+
# and does not send the value as argument.
|
145
|
+
#
|
146
|
+
# * <tt>:only</tt> - In each actions the scope is applied. By default is :all.
|
147
|
+
#
|
148
|
+
# * <tt>:except</tt> - In each actions the scope is not applied. By default is :none.
|
149
|
+
#
|
150
|
+
# * <tt>:key</tt> - The key in the params hash expected to find the scope.
|
151
|
+
# Defaults to the scope name.
|
152
|
+
#
|
153
|
+
# * <tt>:default</tt> - Default value for the scope. Whenever supplied the scope
|
154
|
+
# is always called. This is useful to add easy pagination!
|
155
|
+
#
|
156
|
+
def has_scope(*scopes)
|
157
|
+
options = scopes.extract_options!
|
158
|
+
|
159
|
+
options.symbolize_keys!
|
160
|
+
options.assert_valid_keys(:on, :boolean, :key, :only, :except, :default)
|
161
|
+
|
162
|
+
acts_as_has_scopes!
|
163
|
+
|
164
|
+
scope_target = options.delete(:on) || @@_parent_block_name || self.resources_configuration[:self][:instance_name]
|
165
|
+
target_config = self.scopes_configuration[scope_target.to_sym] ||= {}
|
166
|
+
|
167
|
+
scopes.each do |scope|
|
168
|
+
target_config[scope] ||= {}
|
169
|
+
target_config[scope][:key] = options[:key] || scope
|
170
|
+
target_config[scope][:only] = Array(options[:only])
|
171
|
+
target_config[scope][:except] = Array(options[:except])
|
172
|
+
target_config[scope][:boolean] = options[:boolean] if options.key?(:boolean)
|
173
|
+
target_config[scope][:default] = options[:default] if options.key?(:default)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Load scopes from another controller into the current controller.
|
178
|
+
#
|
179
|
+
# You can give :on as option if you want to load just a set of the another
|
180
|
+
# controller scope.
|
181
|
+
#
|
182
|
+
# class TasksController < InheritedResources::Base
|
183
|
+
# belongs_to :project
|
184
|
+
# load_scopes_from CompaniesController # load all scopes
|
185
|
+
# load_scopes_from ProjectsController, :on => :project # load scopes that apply on :project
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# Whenever called inside a belongs to block, the :on value is guessed:
|
189
|
+
#
|
190
|
+
# class TasksController < InheritedResources::Base
|
191
|
+
# belongs_to :project do
|
192
|
+
# load_scopes_from ProjectsController # load scopes that apply on :project
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# load_scopes_from CompaniesController # load all companies controller scopes
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
def load_scopes_from(scopes_controller, options={})
|
199
|
+
to_merge = if on = options.delete(:on) || @@_parent_block_name
|
200
|
+
scopes_controller.send(:scopes_configuration).slice(on)
|
201
|
+
else
|
202
|
+
scopes_controller.send(:scopes_configuration)
|
203
|
+
end
|
204
|
+
|
205
|
+
acts_as_has_scopes! # Add the module before merging scopes.
|
206
|
+
|
207
|
+
self.scopes_configuration.deep_merge!(to_merge)
|
208
|
+
end
|
209
|
+
|
296
210
|
# Defines that this controller belongs to another resource.
|
297
211
|
#
|
298
212
|
# belongs_to :projects
|
299
213
|
#
|
214
|
+
# == Options
|
215
|
+
#
|
216
|
+
# * <tt>:parent_class</tt> - Allows you to specify what is the parent class.
|
217
|
+
#
|
218
|
+
# belongs_to :project, :parent_class => AdminProject
|
219
|
+
#
|
220
|
+
# * <tt>:class_name</tt> - Also allows you to specify the parent class, but you should
|
221
|
+
# give a string. Added for ActiveRecord belongs to compatibility.
|
222
|
+
#
|
223
|
+
# * <tt>:instance_name</tt> - The instance variable name. By default is the name of the association.
|
224
|
+
#
|
225
|
+
# belongs_to :project, :instance_name => :my_project
|
226
|
+
#
|
227
|
+
# * <tt>:finder</tt> - Specifies which method should be called to instantiate the parent.
|
228
|
+
#
|
229
|
+
# belongs_to :project, :finder => :find_by_title!
|
230
|
+
#
|
231
|
+
# This will make your projects be instantiated as:
|
232
|
+
#
|
233
|
+
# Project.find_by_title!(params[:project_id])
|
234
|
+
#
|
235
|
+
# Instead of:
|
236
|
+
#
|
237
|
+
# Project.find(params[:project_id])
|
238
|
+
#
|
239
|
+
# * <tt>:param</tt> - Allows you to specify params key to retrieve the id.
|
240
|
+
# Default is :association_id, which in this case is :project_id.
|
241
|
+
#
|
242
|
+
# * <tt>:route_name</tt> - Allows you to specify what is the route name in your url
|
243
|
+
# helper. By default is association name.
|
244
|
+
#
|
245
|
+
# * <tt>:collection_name</tt> - Tell how to retrieve the next collection. Let's
|
246
|
+
# suppose you have Tasks which belongs to Projects
|
247
|
+
# which belongs to companies. This will do somewhere
|
248
|
+
# down the road:
|
249
|
+
#
|
250
|
+
# @company.projects
|
251
|
+
#
|
252
|
+
# But if you want to retrieve instead:
|
253
|
+
#
|
254
|
+
# @company.admin_projects
|
255
|
+
#
|
256
|
+
# You supply the collection name.
|
257
|
+
#
|
258
|
+
# * <tt>:polymorphic</tt> - Tell the association is polymorphic.
|
259
|
+
#
|
260
|
+
# * <tt>:singleton</tt> - Tell it's a singleton association.
|
261
|
+
#
|
262
|
+
# * <tt>:optional</tt> - Tell the association is optional (it's a special
|
263
|
+
# type of polymorphic association)
|
264
|
+
#
|
300
265
|
def belongs_to(*symbols, &block)
|
301
266
|
options = symbols.extract_options!
|
302
267
|
|
@@ -309,21 +274,19 @@ module InheritedResources #:nodoc:
|
|
309
274
|
singleton = options.delete(:singleton)
|
310
275
|
polymorphic = options.delete(:polymorphic)
|
311
276
|
|
312
|
-
# Add BelongsToHelpers if we haven't yet.
|
313
277
|
include BelongsToHelpers if self.parents_symbols.empty?
|
314
278
|
|
315
279
|
acts_as_singleton! if singleton
|
316
280
|
acts_as_polymorphic! if polymorphic || optional
|
317
281
|
|
318
282
|
raise ArgumentError, 'You have to give me at least one association name.' if symbols.empty?
|
319
|
-
raise ArgumentError, 'You cannot define multiple associations with
|
283
|
+
raise ArgumentError, 'You cannot define multiple associations with options: #{options.keys.inspect} to belongs to.' unless symbols.size == 1 || options.empty?
|
320
284
|
|
321
|
-
# Set configuration default values
|
322
285
|
symbols.each do |symbol|
|
323
286
|
symbol = symbol.to_sym
|
324
287
|
|
325
288
|
if polymorphic || optional
|
326
|
-
self.parents_symbols << :polymorphic unless self.parents_symbols.include?
|
289
|
+
self.parents_symbols << :polymorphic unless self.parents_symbols.include?(:polymorphic)
|
327
290
|
self.resources_configuration[:polymorphic][:symbols] << symbol
|
328
291
|
self.resources_configuration[:polymorphic][:optional] ||= optional
|
329
292
|
else
|
@@ -333,51 +296,84 @@ module InheritedResources #:nodoc:
|
|
333
296
|
config = self.resources_configuration[symbol] = {}
|
334
297
|
config[:parent_class] = options.delete(:parent_class)
|
335
298
|
config[:parent_class] ||= (options.delete(:class_name) || symbol).to_s.classify.constantize rescue nil
|
336
|
-
config[:collection_name] =
|
337
|
-
config[:instance_name] =
|
338
|
-
config[:param] =
|
339
|
-
config[:finder] =
|
340
|
-
config[:route_name] =
|
299
|
+
config[:collection_name] = options.delete(:collection_name) || symbol.to_s.pluralize.to_sym
|
300
|
+
config[:instance_name] = options.delete(:instance_name) || symbol
|
301
|
+
config[:param] = options.delete(:param) || :"#{symbol}_id"
|
302
|
+
config[:finder] = options.delete(:finder) || :find
|
303
|
+
config[:route_name] = options.delete(:route_name) || symbol
|
341
304
|
end
|
342
305
|
|
343
|
-
# Regenerate url helpers
|
306
|
+
# Regenerate url helpers only once when blocks are given
|
344
307
|
if block_given?
|
345
|
-
|
346
|
-
|
347
|
-
|
308
|
+
raise ArgumentError, "You cannot define multiple associations and give a block to belongs_to." if symbols.size > 1
|
309
|
+
|
310
|
+
unless @@_parent_block_name
|
311
|
+
@@_parent_block_name = symbols.first
|
312
|
+
class_eval(&block)
|
313
|
+
@@_parent_block_name = nil
|
314
|
+
else
|
315
|
+
class_eval(&block)
|
316
|
+
end
|
348
317
|
end
|
318
|
+
|
319
|
+
InheritedResources::UrlHelpers.create_resources_url_helpers!(self) unless @@_parent_block_name
|
349
320
|
end
|
350
321
|
alias :nested_belongs_to :belongs_to
|
351
322
|
|
352
|
-
|
323
|
+
# A quick method to declare polymorphic belongs to.
|
324
|
+
#
|
325
|
+
def polymorphic_belongs_to(*symbols, &block)
|
326
|
+
options = symbols.extract_options!
|
327
|
+
options.merge!(:polymorphic => true)
|
328
|
+
belongs_to(*symbols << options, &block)
|
329
|
+
end
|
353
330
|
|
354
|
-
#
|
355
|
-
# You can call this method to define your controller as singleton.
|
331
|
+
# A quick method to declare singleton belongs to.
|
356
332
|
#
|
357
|
-
def
|
358
|
-
|
359
|
-
|
333
|
+
def singleton_belongs_to(*symbols, &block)
|
334
|
+
options = symbols.extract_options!
|
335
|
+
options.merge!(:singleton => true)
|
336
|
+
belongs_to(*symbols << options, &block)
|
337
|
+
end
|
338
|
+
|
339
|
+
# A quick method to declare optional belongs to.
|
340
|
+
#
|
341
|
+
def optional_belongs_to(*symbols, &block)
|
342
|
+
options = symbols.extract_options!
|
343
|
+
options.merge!(:optional => true)
|
344
|
+
belongs_to(*symbols << options, &block)
|
345
|
+
end
|
346
|
+
|
347
|
+
private
|
348
|
+
|
349
|
+
def acts_as_singleton! #:nodoc:
|
350
|
+
unless self.resources_configuration[:self][:singleton]
|
351
|
+
self.resources_configuration[:self][:singleton] = true
|
360
352
|
include SingletonHelpers
|
361
353
|
actions :all, :except => :index
|
362
354
|
end
|
363
355
|
end
|
364
356
|
|
365
|
-
|
366
|
-
|
367
|
-
#
|
368
|
-
def acts_as_polymorphic!
|
369
|
-
unless self.parents_symbols.include? :polymorphic
|
357
|
+
def acts_as_polymorphic! #:nodoc:
|
358
|
+
unless self.parents_symbols.include?(:polymorphic)
|
370
359
|
include PolymorphicHelpers
|
371
|
-
helper_method :parent, :parent_type, :parent_class
|
360
|
+
helper_method :parent, :parent_type, :parent_class, :parent?
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def acts_as_has_scopes! #:nodoc:
|
365
|
+
if self.scopes_configuration.empty?
|
366
|
+
include HasScopeHelpers
|
367
|
+
helper_method :current_scopes
|
372
368
|
end
|
373
369
|
end
|
374
370
|
|
375
|
-
# Initialize resources class accessors
|
376
|
-
# and setting their default values.
|
371
|
+
# Initialize resources class accessors and set their default values.
|
377
372
|
#
|
378
|
-
def initialize_resources_class_accessors!(base)
|
373
|
+
def initialize_resources_class_accessors!(base) #:nodoc:
|
379
374
|
# Add and protect class accessors
|
380
375
|
base.class_eval do
|
376
|
+
@@_parent_block_name = nil # Initialize parent flag
|
381
377
|
metaklass = (class << self; self; end)
|
382
378
|
|
383
379
|
RESOURCES_CLASS_ACCESSORS.each do |cattr|
|
@@ -411,9 +407,9 @@ module InheritedResources #:nodoc:
|
|
411
407
|
namespaces = base.controller_path.split('/')[0..-2]
|
412
408
|
config[:route_prefix] = namespaces.join('_') unless namespaces.empty?
|
413
409
|
|
414
|
-
# Initialize polymorphic, singleton and belongs_to parameters
|
415
|
-
base.
|
416
|
-
base.
|
410
|
+
# Initialize polymorphic, singleton, scopes and belongs_to parameters
|
411
|
+
base.parents_symbols = []
|
412
|
+
base.scopes_configuration = {}
|
417
413
|
base.resources_configuration[:polymorphic] = { :symbols => [], :optional => false }
|
418
414
|
|
419
415
|
# Create helpers
|
@@ -1,9 +1,9 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
class DumbResponder
|
1
|
+
module InheritedResources
|
2
|
+
# = Dumb Responder
|
3
|
+
#
|
4
|
+
# This responder discards all messages sent to him.
|
5
|
+
#
|
6
|
+
class DumbResponder
|
7
7
|
|
8
8
|
instance_methods.each do |m|
|
9
9
|
undef_method m unless m =~ /^__/
|
@@ -11,6 +11,7 @@ module InheritedResources #:nodoc:
|
|
11
11
|
|
12
12
|
# This is like a good husband, he will just listen everything that his wife
|
13
13
|
# says (which is a lot) without complaining. :)
|
14
|
+
#
|
14
15
|
def method_missing(*args)
|
15
16
|
nil
|
16
17
|
end
|