rc_rails 2.1.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/.gitignore +7 -0
- data/CHANGELOG +355 -0
- data/Gemfile +5 -0
- data/Gemfile.lock.development +117 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +71 -0
- data/Rakefile +33 -0
- data/Todo.txt +1 -0
- data/lib/rc_rails.rb +9 -0
- data/lib/resources_controller/actions.rb +147 -0
- data/lib/resources_controller/active_record/saved.rb +15 -0
- data/lib/resources_controller/helper.rb +123 -0
- data/lib/resources_controller/include_actions.rb +37 -0
- data/lib/resources_controller/named_route_helper.rb +154 -0
- data/lib/resources_controller/railtie.rb +14 -0
- data/lib/resources_controller/request_path_introspection.rb +83 -0
- data/lib/resources_controller/resource_methods.rb +32 -0
- data/lib/resources_controller/singleton_actions.rb +21 -0
- data/lib/resources_controller/specification.rb +119 -0
- data/lib/resources_controller/version.rb +3 -0
- data/lib/resources_controller.rb +849 -0
- data/resources_controller.gemspec +29 -0
- data/spec/app/database.yml +5 -0
- data/spec/app/views/accounts/show.html.erb +0 -0
- data/spec/app/views/addresses/edit.html.erb +0 -0
- data/spec/app/views/addresses/index.html.erb +0 -0
- data/spec/app/views/addresses/new.html.erb +0 -0
- data/spec/app/views/addresses/show.html.erb +0 -0
- data/spec/app/views/admin/forums/create.html.erb +0 -0
- data/spec/app/views/admin/forums/destroy.html.erb +0 -0
- data/spec/app/views/admin/forums/edit.html.erb +0 -0
- data/spec/app/views/admin/forums/index.html.erb +0 -0
- data/spec/app/views/admin/forums/new.html.erb +0 -0
- data/spec/app/views/admin/forums/show.html.erb +0 -0
- data/spec/app/views/admin/forums/update.html.erb +0 -0
- data/spec/app/views/comments/edit.html.erb +0 -0
- data/spec/app/views/comments/index.html.erb +0 -0
- data/spec/app/views/comments/new.html.erb +0 -0
- data/spec/app/views/comments/show.html.erb +0 -0
- data/spec/app/views/forum_posts/edit.html.erb +0 -0
- data/spec/app/views/forum_posts/index.html.erb +0 -0
- data/spec/app/views/forum_posts/new.html.erb +0 -0
- data/spec/app/views/forum_posts/show.html.erb +0 -0
- data/spec/app/views/forums/create.html.erb +0 -0
- data/spec/app/views/forums/destroy.html.erb +0 -0
- data/spec/app/views/forums/edit.html.erb +0 -0
- data/spec/app/views/forums/index.html.erb +0 -0
- data/spec/app/views/forums/new.html.erb +0 -0
- data/spec/app/views/forums/show.html.erb +0 -0
- data/spec/app/views/forums/update.html.erb +0 -0
- data/spec/app/views/infos/edit.html.erb +0 -0
- data/spec/app/views/infos/show.html.erb +0 -0
- data/spec/app/views/interests/index.html.erb +0 -0
- data/spec/app/views/interests/show.html.erb +0 -0
- data/spec/app/views/owners/edit.html.erb +0 -0
- data/spec/app/views/owners/new.html.erb +0 -0
- data/spec/app/views/owners/show.html.erb +0 -0
- data/spec/app/views/tags/index.html.erb +0 -0
- data/spec/app/views/tags/new.html.erb +0 -0
- data/spec/app/views/tags/show.html.erb +0 -0
- data/spec/app/views/users/edit.html.erb +0 -0
- data/spec/app/views/users/index.html.erb +0 -0
- data/spec/app/views/users/show.html.erb +0 -0
- data/spec/app.rb +315 -0
- data/spec/controllers/accounts_controller_spec.rb +77 -0
- data/spec/controllers/addresses_controller_spec.rb +346 -0
- data/spec/controllers/admin_forums_controller_spec.rb +638 -0
- data/spec/controllers/comments_controller_spec.rb +380 -0
- data/spec/controllers/comments_controller_with_models_spec.rb +202 -0
- data/spec/controllers/forum_posts_controller_spec.rb +426 -0
- data/spec/controllers/forums_controller_spec.rb +694 -0
- data/spec/controllers/infos_controller_spec.rb +71 -0
- data/spec/controllers/interests_controller_via_forum_spec.rb +80 -0
- data/spec/controllers/interests_controller_via_user_spec.rb +114 -0
- data/spec/controllers/owners_controller_spec.rb +277 -0
- data/spec/controllers/resource_saved_spec.rb +47 -0
- data/spec/controllers/resource_service_in_forums_controller_spec.rb +37 -0
- data/spec/controllers/resource_service_in_infos_controller_spec.rb +36 -0
- data/spec/controllers/resource_service_in_interests_controller_via_forum_spec.rb +51 -0
- data/spec/controllers/tags_controller_spec.rb +83 -0
- data/spec/controllers/tags_controller_via_account_info_spec.rb +131 -0
- data/spec/controllers/tags_controller_via_forum_post_comment_spec.rb +144 -0
- data/spec/controllers/tags_controller_via_forum_post_spec.rb +133 -0
- data/spec/controllers/tags_controller_via_forum_spec.rb +173 -0
- data/spec/controllers/tags_controller_via_user_address_spec.rb +130 -0
- data/spec/controllers/users_controller_spec.rb +248 -0
- data/spec/lib/action_view_helper_spec.rb +143 -0
- data/spec/lib/bug_0001_spec.rb +22 -0
- data/spec/lib/include_actions_spec.rb +35 -0
- data/spec/lib/load_enclosing_resources_spec.rb +245 -0
- data/spec/lib/request_path_introspection_spec.rb +130 -0
- data/spec/lib/resource_methods_spec.rb +204 -0
- data/spec/lib/resources_controller_spec.rb +57 -0
- data/spec/models/comment_saved_spec.rb +24 -0
- data/spec/rspec_generator_task.rb +105 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/verify_rcov.rb +52 -0
- metadata +193 -0
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
require 'resources_controller/active_record/saved'
|
|
2
|
+
require 'resources_controller/railtie' if defined?(Rails)
|
|
3
|
+
|
|
4
|
+
require 'resources_controller/actions'
|
|
5
|
+
require 'resources_controller/helper'
|
|
6
|
+
require 'resources_controller/include_actions'
|
|
7
|
+
require 'resources_controller/named_route_helper'
|
|
8
|
+
require 'resources_controller/request_path_introspection'
|
|
9
|
+
require 'resources_controller/resource_methods'
|
|
10
|
+
require 'resources_controller/singleton_actions'
|
|
11
|
+
require 'resources_controller/specification'
|
|
12
|
+
|
|
13
|
+
# With resources_controller you can quickly add
|
|
14
|
+
# an ActiveResource compliant controller for your your RESTful models.
|
|
15
|
+
#
|
|
16
|
+
# = Examples
|
|
17
|
+
# Here are some examples - for more on how to use RC go to the Usage section at the bottom,
|
|
18
|
+
# for syntax head to resources_controller_for
|
|
19
|
+
#
|
|
20
|
+
# ==== Example 1: Super simple usage
|
|
21
|
+
# Here's a simple example of how it works with a Forums has many Posts model:
|
|
22
|
+
#
|
|
23
|
+
# class ForumsController < ApplicationController
|
|
24
|
+
# resources_controller_for :forums
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# Your controller will get the standard CRUD actions, @forum will be set in member actions, @forums in
|
|
28
|
+
# index.
|
|
29
|
+
#
|
|
30
|
+
# ==== Example 2: Specifying enclosing resources
|
|
31
|
+
# class PostsController < ApplicationController
|
|
32
|
+
# resources_controller_for :posts, :in => :forum
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# As above, but the controller will load @forum on every action, and use @forum to find and create @posts
|
|
36
|
+
#
|
|
37
|
+
# ==== Wildcard enclosing resources
|
|
38
|
+
# All of the above examples will work for any routes that match what it specified
|
|
39
|
+
#
|
|
40
|
+
# PATH RESOURCES CONTROLLER WILL DO:
|
|
41
|
+
#
|
|
42
|
+
# Example 1 /forums @forums = Forum.find(:all)
|
|
43
|
+
#
|
|
44
|
+
# /users/2/forums @user = User.find(2)
|
|
45
|
+
# @forums = @user.forums.find(:all)
|
|
46
|
+
#
|
|
47
|
+
# Example 2 /posts This won't work as the controller specified
|
|
48
|
+
# that :posts are :in => :forum
|
|
49
|
+
#
|
|
50
|
+
# /forums/2/posts @forum = Forum.find(2)
|
|
51
|
+
# @posts = @forum.posts.find(:all)
|
|
52
|
+
#
|
|
53
|
+
# /sites/4/forums/3/posts @site = Site.find(4)
|
|
54
|
+
# @forum = @site.forums.find(3)
|
|
55
|
+
# @posts = @forum.posts.find(:all)
|
|
56
|
+
#
|
|
57
|
+
# /users/2/posts/1 This won't work as the controller specified
|
|
58
|
+
# that :posts are :in => :forum
|
|
59
|
+
#
|
|
60
|
+
#
|
|
61
|
+
# It is up to you which routes to open to the controller (in config/routes.rb). When
|
|
62
|
+
# you do, RC will use the route segments to drill down to the specified resource. This means
|
|
63
|
+
# that if User 3 does not have Post 5, then /users/3/posts/5 will raise a RecordNotFound Error.
|
|
64
|
+
# You dont' have to write any extra code to do this oft repeated controller pattern.
|
|
65
|
+
#
|
|
66
|
+
# With RC, your route specification flows through to the controller - no need to repeat yourself.
|
|
67
|
+
#
|
|
68
|
+
# If you don't want to have RC match wildcard resources just pass :load_enclosing => false
|
|
69
|
+
#
|
|
70
|
+
# resources_controller_for :posts, :in => :forum, :load_enclosing => false
|
|
71
|
+
#
|
|
72
|
+
# ==== Example 3: Singleton resource
|
|
73
|
+
# Here's an example of a singleton, the account pattern that is so common.
|
|
74
|
+
#
|
|
75
|
+
# class AccountController < ApplicationController
|
|
76
|
+
# resources_controller_for :account, :class => User, :singleton => true do
|
|
77
|
+
# @current_user
|
|
78
|
+
# end
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# Your controller will use the block to find the resource. The @account will be assigned to @current_user
|
|
82
|
+
#
|
|
83
|
+
# ==== Example 4: Allowing PostsController to be used all over
|
|
84
|
+
# First thing to do is remove :in => :forum
|
|
85
|
+
#
|
|
86
|
+
# class PostsController < ApplicationController
|
|
87
|
+
# resources_controller_for :posts
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# This will now work for /users/2/posts.
|
|
91
|
+
#
|
|
92
|
+
# ==== Example 4 and a bit: Mapping non standard resources
|
|
93
|
+
# How about /account/posts? The account is found in a non standard way - RC won't be able
|
|
94
|
+
# to figure out how tofind it if it appears in the route. So we give it some help.
|
|
95
|
+
#
|
|
96
|
+
# (in PostsController)
|
|
97
|
+
#
|
|
98
|
+
# map_enclosing_resource :account, :singleton => true, :class => User, :find => :current_user
|
|
99
|
+
#
|
|
100
|
+
# Now, if :account apears in any part of a route (for PostsController) it will be mapped to
|
|
101
|
+
# (in this case) the current_user method of teh PostsController.
|
|
102
|
+
#
|
|
103
|
+
# To make the :account mapping available to all, just chuck it in ApplicationController
|
|
104
|
+
#
|
|
105
|
+
# This will work for any resource which can't be inferred from its route segment name
|
|
106
|
+
#
|
|
107
|
+
# map_enclosing_resource :users, :segment => :peeps, :key => 'peep_id'
|
|
108
|
+
# map_enclosing_resource :posts, :class => OddlyNamedPostClass
|
|
109
|
+
#
|
|
110
|
+
# ==== Example 5: Singleton association
|
|
111
|
+
# Here's another singleton example - one where it corresponds to a has_one or belongs_to association
|
|
112
|
+
#
|
|
113
|
+
# class ImageController < ApplicationController
|
|
114
|
+
# resources_controller_for :image, :singleton => true
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# When invoked with /users/3/image RC will find @user, and use @user.image to find the resource, and
|
|
118
|
+
# @user.build_image, to create a new resource.
|
|
119
|
+
#
|
|
120
|
+
# ==== Example 6: :resource_path (equivalent resource path): aliasing a named route to a RESTful route
|
|
121
|
+
#
|
|
122
|
+
# You may have a named route that maps a url to a particular controller and action,
|
|
123
|
+
# this causes resources_controller problems as it relies on the route to load the
|
|
124
|
+
# resources. You can get around this by specifying :resource_path as a param in routes.rb
|
|
125
|
+
#
|
|
126
|
+
# map.root :controller => :forums, :action => :index, :resource_path => '/forums'
|
|
127
|
+
#
|
|
128
|
+
# When the controller is invoked via the '' url, rc will use :resource_path to recognize the
|
|
129
|
+
# route.
|
|
130
|
+
#
|
|
131
|
+
# This is only necessary if you have wildcard enclosing resources enabled (the default)
|
|
132
|
+
#
|
|
133
|
+
# ==== Putting it all together
|
|
134
|
+
#
|
|
135
|
+
# An exmaple app
|
|
136
|
+
#
|
|
137
|
+
# config/routes.rb:
|
|
138
|
+
#
|
|
139
|
+
# map.resource :account do |account|
|
|
140
|
+
# account.resource :image
|
|
141
|
+
# account.resources :posts
|
|
142
|
+
# end
|
|
143
|
+
#
|
|
144
|
+
# map.resources :users do |user|
|
|
145
|
+
# user.resource :image
|
|
146
|
+
# user.resources :posts
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# map.resources :forums do |forum|
|
|
150
|
+
# forum.resources :posts
|
|
151
|
+
# forum.resource :image
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# map.root :controller => :forums, :action => :index, :resource_path => '/forums'
|
|
155
|
+
#
|
|
156
|
+
# app/controllers:
|
|
157
|
+
#
|
|
158
|
+
# class ApplicationController < ActionController::Base
|
|
159
|
+
# map_enclosing_resource :account, :singleton => true, :find => :current_user
|
|
160
|
+
#
|
|
161
|
+
# def current_user # get it from session or whatnot
|
|
162
|
+
# end
|
|
163
|
+
#
|
|
164
|
+
# class ForumsController < AplicationController
|
|
165
|
+
# resources_controller_for :forums
|
|
166
|
+
# end
|
|
167
|
+
#
|
|
168
|
+
# class PostsController < AplicationController
|
|
169
|
+
# resources_controller_for :posts
|
|
170
|
+
# end
|
|
171
|
+
#
|
|
172
|
+
# class UsersController < AplicationController
|
|
173
|
+
# resources_controller_for :users
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# class ImageController < AplicationController
|
|
177
|
+
# resources_controller_for :image, :singleton => true
|
|
178
|
+
# end
|
|
179
|
+
#
|
|
180
|
+
# class AccountController < ApplicationController
|
|
181
|
+
# resources_controller_for :account, :singleton => true, :find => :current_user
|
|
182
|
+
# end
|
|
183
|
+
#
|
|
184
|
+
# This is how the app will handle the following routes:
|
|
185
|
+
#
|
|
186
|
+
# PATH CONTROLLER WHICH WILL DO:
|
|
187
|
+
#
|
|
188
|
+
# /forums forums @forums = Forum.find(:all)
|
|
189
|
+
#
|
|
190
|
+
# /forums/2/posts posts @forum = Forum.find(2)
|
|
191
|
+
# @posts = @forum.forums.find(:all)
|
|
192
|
+
#
|
|
193
|
+
# /forums/2/image image @forum = Forum.find(2)
|
|
194
|
+
# @image = @forum.image
|
|
195
|
+
#
|
|
196
|
+
# /image <no route>
|
|
197
|
+
#
|
|
198
|
+
# /posts <no route>
|
|
199
|
+
#
|
|
200
|
+
# /users/2/posts/3 posts @user = User.find(2)
|
|
201
|
+
# @post = @user.posts.find(3)
|
|
202
|
+
#
|
|
203
|
+
# /users/2/image POST image @user = User.find(2)
|
|
204
|
+
# @image = @user.build_image(params[:image])
|
|
205
|
+
#
|
|
206
|
+
# /account account @account = self.current_user
|
|
207
|
+
#
|
|
208
|
+
# /account/image image @account = self.current_user
|
|
209
|
+
# @image = @account.image
|
|
210
|
+
#
|
|
211
|
+
# /account/posts/3 PUT posts @account = self.current_user
|
|
212
|
+
# @post = @account.posts.find(3)
|
|
213
|
+
# @post.update_attributes(params[:post])
|
|
214
|
+
#
|
|
215
|
+
# === Views
|
|
216
|
+
#
|
|
217
|
+
# Ok - so how do I write the views?
|
|
218
|
+
#
|
|
219
|
+
# For most cases, just in exactly the way you would expect to. RC sets the instance variables
|
|
220
|
+
# to what they should be.
|
|
221
|
+
#
|
|
222
|
+
# But, in some cases, you are going to have different variables set - for example
|
|
223
|
+
#
|
|
224
|
+
# /users/1/posts => @user, @posts
|
|
225
|
+
# /forums/2/posts => @forum, @posts
|
|
226
|
+
#
|
|
227
|
+
# Here are some options (all are appropriate for different circumstances):
|
|
228
|
+
# * test for the existence of @user or @forum in the view, and display it differently
|
|
229
|
+
# * have two different controllers UserPostsController and ForumPostsController, with different views
|
|
230
|
+
# (and direct the routes to them in routes.rb)
|
|
231
|
+
# * use enclosing_resource - which always refers to the... immediately enclosing resource.
|
|
232
|
+
#
|
|
233
|
+
# Using the last technique, you might write your posts index as follows
|
|
234
|
+
# (here assuming that both Forum and User have .name)
|
|
235
|
+
#
|
|
236
|
+
# <h1>Posts for <%= link_to enclosing_resource_path, "#{enclosing_resource_name.humanize}: #{enclosing_resource.name}" %></h1>
|
|
237
|
+
#
|
|
238
|
+
# <%= render :partial => 'post', :collection => @posts %>
|
|
239
|
+
#
|
|
240
|
+
# Notice *enclosing_resource_name* - this will be something like 'user', or 'post'.
|
|
241
|
+
# Also *enclosing_resource_path* - in RC you get all of the named route helpers relativised to the current resource
|
|
242
|
+
# and enclosing_resource. See NamedRouteHelper for more details.
|
|
243
|
+
#
|
|
244
|
+
# This can useful when writing the _post partial:
|
|
245
|
+
#
|
|
246
|
+
# <p>
|
|
247
|
+
# <%= post.name %>
|
|
248
|
+
# <%= link_to 'edit', edit_resource_path(tag) %>
|
|
249
|
+
# <%= link_to 'destroy', resource_path(tag), :method => :delete %>
|
|
250
|
+
# </p>
|
|
251
|
+
#
|
|
252
|
+
# when viewed at /users/1/posts it will show
|
|
253
|
+
#
|
|
254
|
+
# <p>
|
|
255
|
+
# Cool post
|
|
256
|
+
# <a href="/users/1/posts/1/edit">edit</a>
|
|
257
|
+
# <a href="js nightmare with /users/1/posts/1">delete</a>
|
|
258
|
+
# </p>
|
|
259
|
+
# ...
|
|
260
|
+
#
|
|
261
|
+
# when viewd at /forums/1/posts it will show
|
|
262
|
+
#
|
|
263
|
+
# <p>
|
|
264
|
+
# Other post
|
|
265
|
+
# <a href="/forums/1/posts/3/edit">edit</a>
|
|
266
|
+
# <a href="js nightmare with /forums/1/posts/3">delete</a>
|
|
267
|
+
# </p>
|
|
268
|
+
# ...
|
|
269
|
+
#
|
|
270
|
+
# This is like polymorphic urls, except that RC will just use whatever enclosing resources are loaded to generate the urls/paths.
|
|
271
|
+
#
|
|
272
|
+
# = Usage
|
|
273
|
+
# To use RC, there are just three class methods on controller to learn.
|
|
274
|
+
#
|
|
275
|
+
# resources_controller_for <name>, <options>, <&block>
|
|
276
|
+
#
|
|
277
|
+
# ClassMethods#nested_in <name>, <options>, <&block>
|
|
278
|
+
#
|
|
279
|
+
# map_enclosing_resource <name>, <options>, <&block>
|
|
280
|
+
#
|
|
281
|
+
# === Customising finding and creating
|
|
282
|
+
# If you want to implement something like query params you can override *find_resources*. If you want to change the
|
|
283
|
+
# way your new resources are created you can override *new_resource*.
|
|
284
|
+
#
|
|
285
|
+
# class PostsController < ApplicationController
|
|
286
|
+
# resources_controller_for :posts
|
|
287
|
+
#
|
|
288
|
+
# def find_resources
|
|
289
|
+
# resource_service.find :all, :order => params[:sort_by]
|
|
290
|
+
# end
|
|
291
|
+
#
|
|
292
|
+
# # you can call super to help yourself to the existing implementation
|
|
293
|
+
# def new_resource
|
|
294
|
+
# super.tap {|r| r.ip_address = request.ip_address }
|
|
295
|
+
# end
|
|
296
|
+
#
|
|
297
|
+
# In the same way, you can override *find_resource*.
|
|
298
|
+
#
|
|
299
|
+
# === Writing controller actions
|
|
300
|
+
#
|
|
301
|
+
# You can make use of RC internals to simplify your actions.
|
|
302
|
+
#
|
|
303
|
+
# Here's an example where you want to re-order an acts_as_list model. You define a class method
|
|
304
|
+
# on the model (say *order_by_ids* which takes and array of ids). You can then make use of *resource_service*
|
|
305
|
+
# (which makes use of awesome rails magic) to send correctly scoped messages to your models.
|
|
306
|
+
#
|
|
307
|
+
# Here's how to write an order action
|
|
308
|
+
#
|
|
309
|
+
# def order
|
|
310
|
+
# resource_service.order_by_ids["things_order"]
|
|
311
|
+
# end
|
|
312
|
+
#
|
|
313
|
+
# the route
|
|
314
|
+
#
|
|
315
|
+
# map.resources :things, :collection => {:order => :put}
|
|
316
|
+
#
|
|
317
|
+
# and the view can conatin a scriptaculous drag and drop with param name 'things_order'
|
|
318
|
+
#
|
|
319
|
+
# When this controller is invoked of /things the :order_by_ids message will be sent to the Thing class,
|
|
320
|
+
# when it's invoked by /foos/1/things, then :order_by_ids message will be send to Foo.find(1).things association
|
|
321
|
+
#
|
|
322
|
+
# === using non standard ids
|
|
323
|
+
#
|
|
324
|
+
# Lets say you want to set to_param to login, and use find_by_login
|
|
325
|
+
# for your users in your URLs, with routes as follows:
|
|
326
|
+
#
|
|
327
|
+
# map.reosurces :users do |user|
|
|
328
|
+
# user.resources :addresses
|
|
329
|
+
# end
|
|
330
|
+
#
|
|
331
|
+
# First, the users controller needs to find reosurces using find_by_login
|
|
332
|
+
#
|
|
333
|
+
# class UsersController < ApplicationController
|
|
334
|
+
# resources_controller_for :users
|
|
335
|
+
#
|
|
336
|
+
# protected
|
|
337
|
+
# def find_resource(id = params[:id])
|
|
338
|
+
# resource_service.find_by_login(id)
|
|
339
|
+
# end
|
|
340
|
+
# end
|
|
341
|
+
#
|
|
342
|
+
# This controller will find users (for editing, showing, and destroying) as
|
|
343
|
+
# directed. (this controller will work for any route where user is the
|
|
344
|
+
# last resource, including the /users/dave route)
|
|
345
|
+
#
|
|
346
|
+
# Now you need to specify that the user as enclosing resource needs to be found
|
|
347
|
+
# with find_by_login. For the addresses case above, you would do this:
|
|
348
|
+
#
|
|
349
|
+
# class AddressesController < ApplicationController
|
|
350
|
+
# resources_controller_for :addresses
|
|
351
|
+
# nested_in :user do
|
|
352
|
+
# User.find_by_login(params[:user_id])
|
|
353
|
+
# end
|
|
354
|
+
# end
|
|
355
|
+
#
|
|
356
|
+
# If you wanted to open up more nested resources under user, you could repeat
|
|
357
|
+
# this specification in all such controllers, alternatively, you could map the
|
|
358
|
+
# resource in the ApplicationController, which would be usable by any controller
|
|
359
|
+
#
|
|
360
|
+
# If you know that user is never nested (i.e. /users/dave/addresses), then do this:
|
|
361
|
+
#
|
|
362
|
+
# class ApplicationController < ActionController::Base
|
|
363
|
+
# map_enclosing_resource :user do
|
|
364
|
+
# User.find(params[:user_id])
|
|
365
|
+
# end
|
|
366
|
+
# end
|
|
367
|
+
#
|
|
368
|
+
# or, if user is sometimes nested (i.e. /forums/1/users/dave/addresses), do this:
|
|
369
|
+
#
|
|
370
|
+
# map_enclosing_resource :user do
|
|
371
|
+
# ((enclosing_resource && enclosing_resource.users) || User).find(params[:user_id])
|
|
372
|
+
# end
|
|
373
|
+
#
|
|
374
|
+
# Your Addresses controller will now be the very simple one, and the resource map will
|
|
375
|
+
# load user as specified when it is hit by a route /users/dave/addresses.
|
|
376
|
+
#
|
|
377
|
+
# class AddressesController < ApplicationController
|
|
378
|
+
# resources_controller_for :addresses
|
|
379
|
+
# end
|
|
380
|
+
#
|
|
381
|
+
module ResourcesController
|
|
382
|
+
mattr_accessor :actions, :singleton_actions
|
|
383
|
+
self.actions = ResourcesController::Actions
|
|
384
|
+
self.singleton_actions = ResourcesController::SingletonActions
|
|
385
|
+
|
|
386
|
+
def self.extended(base)
|
|
387
|
+
base.class_eval do
|
|
388
|
+
class_attribute :resource_specification_map
|
|
389
|
+
self.resource_specification_map = {}
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Specifies that this controller is a REST style controller for the named resource
|
|
394
|
+
#
|
|
395
|
+
# Enclosing resources are loaded automatically by default, you can turn this off with
|
|
396
|
+
# :load_enclosing (see options below)
|
|
397
|
+
#
|
|
398
|
+
# resources_controller_for <name>, <options>, <&block>
|
|
399
|
+
#
|
|
400
|
+
# ==== Options:
|
|
401
|
+
# * <tt>:singleton:</tt> (default false) set this to true if the resource is a Singleton
|
|
402
|
+
# * <tt>:find:</tt> (default null) set this to a symbol or Proc to specify how to find the resource.
|
|
403
|
+
# Use this if the resource is found in an unconventional way. Passing a block has the same effect as
|
|
404
|
+
# setting :find => a Proc
|
|
405
|
+
# * <tt>:in:</tt> specify the enclosing resources, by name. ClassMethods#nested_in can be used to
|
|
406
|
+
# specify this more fully.
|
|
407
|
+
# * <tt>:load_enclosing:</tt> (default true) loads enclosing resources automatically.
|
|
408
|
+
# * <tt>:actions:</tt> (default nil) set this to false if you don't want the default RC actions. Set this
|
|
409
|
+
# to a module to use that module for your own actions.
|
|
410
|
+
# * <tt>:only:</tt> only include the specified actions.
|
|
411
|
+
# * <tt>:except:</tt> include all actions except the specified actions.
|
|
412
|
+
#
|
|
413
|
+
# ===== Options for unconvential use
|
|
414
|
+
# (otherwise these are all inferred from the _name_)
|
|
415
|
+
# * <tt>:route:</tt> the route name (without name_prefix) if it can't be inferred from _name_.
|
|
416
|
+
# For a collection resource this should be plural, for a singleton it should be singular.
|
|
417
|
+
# * <tt>:source:</tt> a string or symbol (e.g. :users, or :user). This is used to find the class or association name
|
|
418
|
+
# * <tt>:class:</tt> a Class. This is the class of the resource (if it can't be inferred from _name_ or :source)
|
|
419
|
+
# * <tt>:segment:</tt> (e.g. 'users') the segment name in the route that is matched
|
|
420
|
+
#
|
|
421
|
+
# === The :in option
|
|
422
|
+
# The default behavior is to set up before filters that load the enclosing resource, and to use associations on
|
|
423
|
+
# that model to find and create the resources. See ClassMethods#nested_in for more details on this, and
|
|
424
|
+
# customising the default behaviour.
|
|
425
|
+
#
|
|
426
|
+
# === load_enclosing_resources
|
|
427
|
+
# By default, a before_filter is added by resources_controller called :load_enclosing_resources - which
|
|
428
|
+
# does all the work of loading the enclosing resources. You can use ActionControllers standard filter
|
|
429
|
+
# mechanisms to control when this filter is invoked. For example - you can choose not to load resources
|
|
430
|
+
# on an action
|
|
431
|
+
#
|
|
432
|
+
# resources_controller_for :foos
|
|
433
|
+
# skip_before_filter :load_enclosing_resources, :only => :static_page
|
|
434
|
+
#
|
|
435
|
+
# Or, you can change the order of when the filter is invoked by adding the filter call yourself (rc will
|
|
436
|
+
# only add the filter if it doesn't exist)
|
|
437
|
+
#
|
|
438
|
+
# before_filter :do_something
|
|
439
|
+
# prepend_before_filter :load_enclosing_resources
|
|
440
|
+
# resources_controller_for :foos
|
|
441
|
+
# before_filter :do_something_else # chain => [:load_enclosing_resources, :do_something, :do_something_else]
|
|
442
|
+
#
|
|
443
|
+
# === Default actions module
|
|
444
|
+
# If you have your own actions module you prefer to use other than the standard resources_controller ones
|
|
445
|
+
# you can set ResourcesController.actions to that module to have this be included by default
|
|
446
|
+
#
|
|
447
|
+
# ResourcesController.actions = MyAwesomeActions
|
|
448
|
+
# ResourcesController.singleton_actions = MyAweseomeSingletonActions
|
|
449
|
+
#
|
|
450
|
+
# class AwesomenessController < ApplicationController
|
|
451
|
+
# resources_controller_for :awesomenesses # includes MyAwesomeActions by default
|
|
452
|
+
# end
|
|
453
|
+
def resources_controller_for(name, options = {}, &block)
|
|
454
|
+
options.assert_valid_keys(:class, :source, :singleton, :actions, :in, :find, :load_enclosing, :route, :segment, :as, :only, :except, :resource_methods)
|
|
455
|
+
when_options = {:only => options.delete(:only), :except => options.delete(:except)}
|
|
456
|
+
|
|
457
|
+
unless included_modules.include? ResourcesController::InstanceMethods
|
|
458
|
+
class_attribute :specifications, :route_name
|
|
459
|
+
hide_action :specifications, :route_name
|
|
460
|
+
extend ResourcesController::ClassMethods
|
|
461
|
+
helper ResourcesController::Helper
|
|
462
|
+
include ResourcesController::InstanceMethods, ResourcesController::NamedRouteHelper
|
|
463
|
+
include ResourcesController::ResourceMethods unless options.delete(:resource_methods) == false || included_modules.include?(ResourcesController::ResourceMethods)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
before_filter(:load_enclosing_resources, when_options.dup) unless load_enclosing_resources_filter_exists?
|
|
467
|
+
|
|
468
|
+
self.specifications = []
|
|
469
|
+
specifications << '*' unless options.delete(:load_enclosing) == false
|
|
470
|
+
|
|
471
|
+
unless (actions = options.delete(:actions)) == false
|
|
472
|
+
actions ||= options[:singleton] ? ResourcesController.singleton_actions : ResourcesController.actions
|
|
473
|
+
include_actions actions, when_options
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
route = (options.delete(:route) || name).to_s
|
|
477
|
+
name = options[:singleton] ? name.to_s : name.to_s.singularize
|
|
478
|
+
self.route_name = options[:singleton] ? route : route.singularize
|
|
479
|
+
|
|
480
|
+
nested_in(*options.delete(:in)) if options[:in]
|
|
481
|
+
|
|
482
|
+
class_attribute :resource_specification, :instance_writer => false
|
|
483
|
+
self.resource_specification = Specification.new(name, options, &block)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Creates a resource specification mapping. Use this to specify how to find an enclosing resource that
|
|
487
|
+
# does not obey usual rails conventions. Most commonly this would be a singleton resource.
|
|
488
|
+
#
|
|
489
|
+
# See Specification#new for details of how to call this
|
|
490
|
+
def map_enclosing_resource(name, options = {}, &block)
|
|
491
|
+
spec = Specification.new(name, options, &block)
|
|
492
|
+
resource_specification_map[spec.segment] = spec
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# this will be deprecated soon as it's badly named - use map_enclosing_resource
|
|
496
|
+
def map_resource(*args, &block)
|
|
497
|
+
map_enclosing_resource(*args, &block)
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Include the specified module, optionally specifying which public methods to include, for example:
|
|
501
|
+
# include_actions ActionMixin, :only => :index
|
|
502
|
+
# include_actions ActionMixin, :except => [:create, :new]
|
|
503
|
+
def include_actions(mixin, options = {})
|
|
504
|
+
mixin.extend(IncludeActions) unless mixin.respond_to?(:include_actions)
|
|
505
|
+
mixin.include_actions(self, options)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
private
|
|
509
|
+
def load_enclosing_resources_filter_exists?
|
|
510
|
+
if respond_to?(:find_filter) # BC 2.0-stable branch
|
|
511
|
+
find_filter(:load_enclosing_resources)
|
|
512
|
+
else
|
|
513
|
+
_process_action_callbacks.detect {|c| c.filter == :load_enclosing_resources}
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
module ClassMethods
|
|
518
|
+
# Specifies that this controller has a particular enclosing resource.
|
|
519
|
+
#
|
|
520
|
+
# This can be called with an array of symbols (in which case options can't be specified) or
|
|
521
|
+
# a symbol with options.
|
|
522
|
+
#
|
|
523
|
+
# See Specification#new for details of how to call this.
|
|
524
|
+
def nested_in(*names, &block)
|
|
525
|
+
options = names.extract_options!
|
|
526
|
+
raise ArgumentError, "when giving more than one nesting, you may not specify options or a block" if names.length > 1 and (block_given? or options.length > 0)
|
|
527
|
+
|
|
528
|
+
# convert :polymorphic option to '?'
|
|
529
|
+
if options.delete(:polymorphic)
|
|
530
|
+
raise ArgumentError, "when specifying :polymorphic => true, no block or other options may be given" if block_given? or options.length > 0
|
|
531
|
+
names = ["?#{names.first}"]
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# ignore first '*' if it has already been specified by :load_enclosing == true
|
|
535
|
+
names.shift if specifications == ['*'] && names.first == '*'
|
|
536
|
+
|
|
537
|
+
names.each do |name|
|
|
538
|
+
ensure_sane_wildcard if name == '*'
|
|
539
|
+
specifications << (name.to_s =~ /^(\*|\?(.*))$/ ? name.to_s : Specification.new(name, options, &block))
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
private
|
|
544
|
+
# ensure that specifications array is determinate w.r.t route matching
|
|
545
|
+
def ensure_sane_wildcard
|
|
546
|
+
idx = specifications.length
|
|
547
|
+
while (idx -= 1) >= 0
|
|
548
|
+
if specifications[idx] == '*'
|
|
549
|
+
raise ArgumentError, "Can only specify one wildcard '*' in between resource specifications"
|
|
550
|
+
elsif specifications[idx].is_a?(Specification)
|
|
551
|
+
break
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
true
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
module InstanceMethods
|
|
559
|
+
def self.included(controller)
|
|
560
|
+
controller.send :hide_action, *instance_methods
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def resource_service=(service)
|
|
564
|
+
@resource_service = service
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def name_prefix
|
|
568
|
+
@name_prefix ||= ''
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# name of the singular resource
|
|
572
|
+
def resource_name
|
|
573
|
+
resource_specification.name
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# name of the resource collection
|
|
577
|
+
def resources_name
|
|
578
|
+
@resources_name ||= resource_specification.name.pluralize
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# returns the controller's resource class
|
|
582
|
+
def resource_class
|
|
583
|
+
resource_specification.klass
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# returns the controller's current resource.
|
|
587
|
+
def resource
|
|
588
|
+
instance_variable_get("@#{resource_name}")
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# sets the controller's current resource, and
|
|
592
|
+
# decorates the object with a save hook, so we know if it's been saved
|
|
593
|
+
def resource=(record)
|
|
594
|
+
instance_variable_set("@#{resource_name}", record)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# returns the controller's current resources collection
|
|
598
|
+
def resources
|
|
599
|
+
instance_variable_get("@#{resources_name}")
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# sets the controller's current resource collection
|
|
603
|
+
def resources=(collection)
|
|
604
|
+
instance_variable_set("@#{resources_name}", collection)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# returns the immediately enclosing resource
|
|
608
|
+
def enclosing_resource
|
|
609
|
+
enclosing_resources.last
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# returns the name of the immediately enclosing resource
|
|
613
|
+
def enclosing_resource_name
|
|
614
|
+
@enclosing_resource_name
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# returns the resource service for the controller - this will be lazilly created
|
|
618
|
+
# to a ResourceService, or a SingletonResourceService (if :singleton => true)
|
|
619
|
+
def resource_service
|
|
620
|
+
@resource_service ||= resource_specification.singleton? ? SingletonResourceService.new(self) : ResourceService.new(self)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# returns the instance resource_specification
|
|
624
|
+
def resource_specification
|
|
625
|
+
self.class.resource_specification
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# returns an array of the controller's enclosing (nested in) resources
|
|
629
|
+
def enclosing_resources
|
|
630
|
+
@enclosing_resources ||= []
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# returns an array of the collection (non singleton) enclosing resources, this is used for generating routes.
|
|
634
|
+
def enclosing_collection_resources
|
|
635
|
+
@enclosing_collection_resources ||= []
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# NOTE: This method is overly complicated and unecessary. It's much clearer just to keep
|
|
639
|
+
# track of record saves yourself, this is here for BC. For an example of how it should be
|
|
640
|
+
# done look at the actions module in http://github.com/ianwhite/response_for_rc
|
|
641
|
+
#
|
|
642
|
+
# Has the resource been saved successfully?, if no save has been attempted, save the
|
|
643
|
+
# record and return the result
|
|
644
|
+
#
|
|
645
|
+
# This method uses the @resource_saved tracking var, or the model's state itself if
|
|
646
|
+
# that is not available (which means if you do resource.update_attributes, then this
|
|
647
|
+
# method will return the correct result)
|
|
648
|
+
def resource_saved?
|
|
649
|
+
save_resource if @resource_saved.nil? && !resource.validation_attempted?
|
|
650
|
+
@resource_saved = resource.saved? if @resource_saved.nil?
|
|
651
|
+
@resource_saved
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# NOTE: it's clearer to just keep track of record saves yourself, this is here for BC
|
|
655
|
+
# See the comment on #resource_saved?
|
|
656
|
+
#
|
|
657
|
+
# @resource_saved = resource.update_attributes(params[resource_name])
|
|
658
|
+
#
|
|
659
|
+
# Save the resource, and keep track of the result
|
|
660
|
+
def save_resource
|
|
661
|
+
@resource_saved = resource.save
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
private
|
|
665
|
+
# this is the before_filter that loads all specified and wildcard resources
|
|
666
|
+
def load_enclosing_resources
|
|
667
|
+
namespace_segments.each {|segment| update_name_prefix("#{segment}_") }
|
|
668
|
+
specifications.each_with_index do |spec, idx|
|
|
669
|
+
case spec
|
|
670
|
+
when '*' then load_wildcards_from(idx)
|
|
671
|
+
when /^\?(.*)/ then load_wildcard($1)
|
|
672
|
+
else load_enclosing_resource_from_specification(spec)
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# load a wildcard resource by either
|
|
678
|
+
# * matching the segment to mapped resource specification, or
|
|
679
|
+
# * creating one using the segment name
|
|
680
|
+
# Optionally takes a variable name to set the instance variable as (for polymorphic use)
|
|
681
|
+
def load_wildcard(as = nil)
|
|
682
|
+
seg = nesting_segments[enclosing_resources.size] or ResourcesController.raise_resource_mismatch(self)
|
|
683
|
+
|
|
684
|
+
segment = seg[:segment]
|
|
685
|
+
singleton = seg[:singleton]
|
|
686
|
+
|
|
687
|
+
if resource_specification_map[segment]
|
|
688
|
+
spec = resource_specification_map[segment]
|
|
689
|
+
spec = spec.dup.tap {|s| s.as = as} if as
|
|
690
|
+
else
|
|
691
|
+
spec = Specification.new(singleton ? segment : segment.singularize, :singleton => singleton, :as => as)
|
|
692
|
+
end
|
|
693
|
+
load_enclosing_resource_from_specification(spec)
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# loads a series of wildcard resources, from the specified specification idx
|
|
697
|
+
#
|
|
698
|
+
# To do this, we need to figure out where the next specified resource is
|
|
699
|
+
# and how many single wildcards are prior to that. What is left over from
|
|
700
|
+
# the current route enclosing names will be the number of wildcards we need to load
|
|
701
|
+
def load_wildcards_from(start)
|
|
702
|
+
specs = specifications.slice(start..-1)
|
|
703
|
+
encls = nesting_segments.slice(enclosing_resources.size..-1)
|
|
704
|
+
|
|
705
|
+
if spec = specs.find {|s| s.is_a?(Specification)}
|
|
706
|
+
spec_seg = encls.index({:segment => spec.segment, :singleton => spec.singleton?}) or ResourcesController.raise_resource_mismatch(self)
|
|
707
|
+
number_of_wildcards = spec_seg - (specs.index(spec) -1)
|
|
708
|
+
else
|
|
709
|
+
number_of_wildcards = encls.length - (specs.length - 1)
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
number_of_wildcards.times { load_wildcard }
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def load_enclosing_resource_from_specification(spec)
|
|
716
|
+
spec.segment == nesting_segments[enclosing_resources.size][:segment] or ResourcesController.raise_resource_mismatch(self)
|
|
717
|
+
spec.find_from(self).tap do |resource|
|
|
718
|
+
add_enclosing_resource(resource, :name => spec.name, :name_prefix => spec.name_prefix, :is_singleton => spec.singleton?, :as => spec.as)
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def add_enclosing_resource(resource, options = {})
|
|
723
|
+
name = options[:name] || resource.class.name.underscore
|
|
724
|
+
update_name_prefix(options[:name_prefix] || (options[:name_prefix] == false ? '' : "#{name}_"))
|
|
725
|
+
enclosing_resources << resource
|
|
726
|
+
enclosing_collection_resources << resource unless options[:is_singleton]
|
|
727
|
+
instance_variable_set("@enclosing_resource_name", options[:name])
|
|
728
|
+
instance_variable_set("@#{name}", resource)
|
|
729
|
+
instance_variable_set("@#{options[:as]}", resource) if options[:as]
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# The name prefix is used for forwarding urls and will be different depending on
|
|
733
|
+
# which route the controller was invoked by. The resource specifications build
|
|
734
|
+
# up the name prefix as the resources are loaded.
|
|
735
|
+
def update_name_prefix(name_prefix)
|
|
736
|
+
@name_prefix = "#{@name_prefix}#{name_prefix}"
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
# Proxy class to provide a consistent API for resource_service. This is mostly
|
|
741
|
+
# required for Singleton resources. Also allows decoration of the resource service with custom finders
|
|
742
|
+
class ResourceService < ActiveSupport::BasicObject
|
|
743
|
+
attr_reader :controller
|
|
744
|
+
delegate :resource_specification, :resource_class, :enclosing_resource, :to => :controller
|
|
745
|
+
|
|
746
|
+
def initialize(controller)
|
|
747
|
+
@controller = controller
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def method_missing(*args, &block)
|
|
751
|
+
service.send(*args, &block)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def find(*args, &block)
|
|
755
|
+
resource_specification.find ? resource_specification.find_custom(controller) : super
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
# build association on the enclosing resource if there is one, otherwise call new
|
|
759
|
+
def new(*args, &block)
|
|
760
|
+
enclosing_resource ? service.build(*args, &block) : service.new(*args, &block)
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# find the resource
|
|
764
|
+
# If we have a resource service, we call destroy on it with the reosurce id, so that any callbacks can be triggered
|
|
765
|
+
# Otherwise, just call destroy on the resource
|
|
766
|
+
def destroy(*args)
|
|
767
|
+
resource = find(*args)
|
|
768
|
+
if enclosing_resource
|
|
769
|
+
service.destroy(*args)
|
|
770
|
+
resource
|
|
771
|
+
else
|
|
772
|
+
resource.destroy
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def respond_to?(method, include_private = false)
|
|
777
|
+
super || service.respond_to?(method)
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
def service
|
|
781
|
+
@service ||= enclosing_resource ? enclosing_resource.send(resource_specification.source) : resource_class
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
class SingletonResourceService < ResourceService
|
|
786
|
+
def find(*args)
|
|
787
|
+
if resource_specification.find
|
|
788
|
+
resource_specification.find_custom(controller)
|
|
789
|
+
elsif controller.enclosing_resources.size > 0
|
|
790
|
+
enclosing_resource.send(resource_specification.source)
|
|
791
|
+
else
|
|
792
|
+
::ResourcesController.raise_cant_find_singleton(controller.resource_name, controller.resource_class)
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
# build association on the enclosing resource if there is one, otherwise call new
|
|
797
|
+
def new(*args, &block)
|
|
798
|
+
enclosing_resource ? enclosing_resource.send("build_#{resource_specification.source}", *args, &block) : service.new(*args, &block)
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def destroy(*args)
|
|
802
|
+
find.destroy
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def service
|
|
806
|
+
resource_class
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
class CantFindSingleton < RuntimeError #:nodoc:
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
class ResourceMismatch < RuntimeError #:nodoc:
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
class << self
|
|
817
|
+
def raise_cant_find_singleton(name, klass) #:nodoc:
|
|
818
|
+
raise CantFindSingleton, <<-end_str
|
|
819
|
+
Can't get singleton resource from class #{klass.name}. You have have probably done something like:
|
|
820
|
+
|
|
821
|
+
nested_in :#{name}, :singleton => true # <= where this is the first nested_in
|
|
822
|
+
|
|
823
|
+
You should tell resources_controller how to find the singleton resource like this:
|
|
824
|
+
|
|
825
|
+
nested_in :#{name}, :singleton => true do
|
|
826
|
+
#{klass.name}.find(<.. your find args here ..>)
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
Or:
|
|
830
|
+
nested_in :#{name}, :singleton => true, :find => <.. method name or lambda ..>
|
|
831
|
+
|
|
832
|
+
Or, you may be relying on the route to load the resource, in which case you need to give RC some
|
|
833
|
+
help. Do this by mapping the route segment to a resource in the controller, or a parent or mixin
|
|
834
|
+
|
|
835
|
+
map_enclosing_resource :#{name}, :segment => ..., :singleton => true <.. as above ..>
|
|
836
|
+
end_str
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
def raise_resource_mismatch(controller) #:nodoc:
|
|
840
|
+
raise ResourceMismatch, <<-end_str
|
|
841
|
+
resources_controller can't match the route to the resource specification
|
|
842
|
+
path: #{controller.send(:request_path)}
|
|
843
|
+
specification: enclosing: [#{controller.specifications.collect{|s| s.is_a?(Specification) ? ":#{s.segment}" : s}.join(', ')}], resource :#{controller.resource_specification.segment}
|
|
844
|
+
|
|
845
|
+
the successfully loaded enclosing resources are: #{controller.enclosing_resources.join(', ')}
|
|
846
|
+
end_str
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
end
|