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