extended_inherited_resources 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,25 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ##RUBYMINE
22
+ .idea
23
+ .idea/**/*
24
+
25
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 B�a�ej Kosmowski
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,466 @@
1
+ == Extended Inherited Resources
2
+
3
+ Inherited Resources speeds up development by making your controllers inherit
4
+ all restful actions so you just have to focus on what is important. It makes
5
+ your controllers more powerful and cleaner at the same time.
6
+
7
+ Plus, making your controllers follow a pattern, it helps you to write better
8
+ code by following fat models and skinny controllers convention. There is
9
+ a screencast made by Fabio Akita about its features:
10
+
11
+ http://akitaonrails.com/2009/09/01/screencast-real-thin-restful-controllers-with-inherited-resources
12
+
13
+ This Inherited Resources branch is tested and compatible with Rails 2.3.x.
14
+
15
+ == Installation
16
+
17
+ Inherited Resources is available as gem on Gemcutter, so just run the following:
18
+
19
+ sudo gem install inherited_resources --version=1.0
20
+
21
+ If you want it as plugin, just do:
22
+
23
+ script/plugin install git://github.com/josevalim/inherited_resources.git
24
+
25
+ === HasScope
26
+
27
+ Since Inherited Resources 1.0, has_scope is not part of its core anymore.
28
+ However, if you are using has_scope in your application, Inherited Resources
29
+ will handle all the required hooks automatically.
30
+
31
+ has_scope gem is available at:
32
+
33
+ http://github.com/plataformatec/has_scope
34
+
35
+ And can be installed as:
36
+
37
+ sudo gem install has_scope
38
+
39
+ === Responders
40
+
41
+ Since Inherited Resources 1.0, responders are not part of its core anymore,
42
+ but is set as Inherited Resources dependency and it's used by default by
43
+ InheritedResources controllers. Be sure to check the documentation to see
44
+ how it will change your application:
45
+
46
+ http://github.com/plataformatec/responders
47
+
48
+ And it can be installed as:
49
+
50
+ sudo gem install responders
51
+
52
+ Using responders will set the flash message to :notice and :alert. You can change
53
+ that through the following configuration value:
54
+
55
+ InheritedResources.flash_keys = [ :success, :failure ]
56
+
57
+ == Rspec known bug
58
+
59
+ Rspec monkey patches a couple of controller methods in a way that Controller specs
60
+ (with integrate views true or false) and Inherited Resources are not compatible.
61
+
62
+ However, since your controllers inherit from InheritedResources::Base, they are
63
+ already unit-tested in the plugin, so there is no need to test them again in Rspec.
64
+
65
+ You should test things like url redirection and associations in your integration
66
+ specs.
67
+
68
+ == Basic Usage
69
+
70
+ To use Inherited Resources you just have to inherit (duh) it:
71
+
72
+ class ProjectsController < InheritedResources::Base
73
+ end
74
+
75
+ And all actions are defined and working, check it! Your projects collection
76
+ (in the index action) is still available in the instance variable @projects
77
+ and your project resource (all other actions) is available as @project.
78
+
79
+ The next step is to define which mime types this controller provides:
80
+
81
+ class ProjectsController < InheritedResources::Base
82
+ respond_to :html, :xml, :json
83
+ end
84
+
85
+ You can also specify them based per action:
86
+
87
+ class ProjectsController < InheritedResources::Base
88
+ respond_to :html, :xml, :json
89
+ respond_to :js, :only => :create
90
+ respond_to :iphone, :except => [ :edit, :update ]
91
+ end
92
+
93
+ For each request, it first checkes if the "controller/action.format" file is
94
+ available (for example "projects/create.xml") and if it's not, it checks if
95
+ the resource respond to :to_format (in this case, :to_xml). Otherwise returns 404.
96
+
97
+ Another option is to specify which actions the controller will inherit from
98
+ the InheritedResources::Base:
99
+
100
+ class ProjectsController < InheritedResources::Base
101
+ actions :index, :show, :new, :create
102
+ end
103
+
104
+ Or:
105
+
106
+ class ProjectsController < InheritedResources::Base
107
+ actions :all, :except => [ :edit, :update, :destroy ]
108
+ end
109
+
110
+ In your views, you will get the following helpers:
111
+
112
+ resource #=> @project
113
+ collection #=> @projects
114
+ resource_class #=> Project
115
+
116
+ As you might expect, collection (@projects instance variable) is only available
117
+ on index actions.
118
+
119
+ If for some reason you cannot inherit from InheritedResources::Base, you can
120
+ call inherit_resources in your controller class scope:
121
+
122
+ class AccountsController < ApplicationController
123
+ inherit_resources
124
+ end
125
+
126
+ == Overwriting defaults
127
+
128
+ Whenever you inherit from InheritedResources, several defaults are assumed.
129
+ For example you can have an AccountsController to account management while the
130
+ resource is an User:
131
+
132
+ class AccountsController < InheritedResources::Base
133
+ defaults :resource_class => User, :collection_name => 'users', :instance_name => 'user'
134
+ end
135
+
136
+ In the case above, in your views you will have @users and @user variables, but
137
+ the routes used will still be accounts_url and account_url. If you plan also to
138
+ change the routes, you can use :route_collection_name and :route_instance_name.
139
+
140
+ Namespaced controllers work out of the box, but if you need to specify a
141
+ different route prefix, you can do the following:
142
+
143
+ class Administrators::PeopleController < InheritedResources::Base
144
+ defaults :route_prefix => 'admin'
145
+ end
146
+
147
+ Then your named routes will be: 'admin_people_url', 'admin_person_url' instead
148
+ of 'administrators_people_url' and 'administrators_person_url'.
149
+
150
+ If you want to customize how resources are retrieved you can overwrite
151
+ collection and resource methods. The first is called on index action and the
152
+ second on all other actions. Let's suppose you want to add pagination to your
153
+ projects collection:
154
+
155
+ class ProjectsController < InheritedResources::Base
156
+ protected
157
+ def collection
158
+ @projects ||= end_of_association_chain.paginate(:page => params[:page])
159
+ end
160
+ end
161
+
162
+ The end_of_association_chain returns your resource after nesting all associations
163
+ and scopes (more about this below).
164
+
165
+ InheritedResources also introduces another method called begin_of_association_chain.
166
+ It's mostly used when you want to create resources based on the @current_user and
167
+ you have urls like "account/projects". In such cases, you have to do
168
+ @current_user.projects.find or @current_user.projects.build in your actions.
169
+
170
+ You can deal with it just doing:
171
+
172
+ class ProjectsController < InheritedResources::Base
173
+ protected
174
+ def begin_of_association_chain
175
+ @current_user
176
+ end
177
+ end
178
+
179
+ == Overwriting actions
180
+
181
+ Let's suppose that after destroying a project you want to redirect to your
182
+ root url instead of redirecting to projects url. You just have to do:
183
+
184
+ class ProjectsController < InheritedResources::Base
185
+ def destroy
186
+ super do |format|
187
+ format.html { redirect_to root_url }
188
+ end
189
+ end
190
+ end
191
+
192
+ You are opening your action and giving the parent action a new behavior. On
193
+ the other hand, I have to agree that calling super is not very readable. That's
194
+ why all methods have aliases. So this is equivalent:
195
+
196
+ class ProjectsController < InheritedResources::Base
197
+ def destroy
198
+ destroy! do |format|
199
+ format.html { redirect_to root_url }
200
+ end
201
+ end
202
+ end
203
+
204
+ Even more, since most of the times when you change a create, update or destroy
205
+ action is because you want to to change to where it redirects, a shortcut is
206
+ provided. So you can do:
207
+
208
+ class ProjectsController < InheritedResources::Base
209
+ def destroy
210
+ destroy!{ root_url }
211
+ end
212
+ end
213
+
214
+ Now let's suppose that before create a project you have to do something special
215
+ but you don't want to create a before filter for it:
216
+
217
+ class ProjectsController < InheritedResources::Base
218
+ def create
219
+ @project = Project.new(params[:project])
220
+ @project.something_special!
221
+ create!
222
+ end
223
+ end
224
+
225
+ Yes, that simple! The nice part is since you already set the instance variable
226
+ @project, it will not build a project again.
227
+
228
+ Before we finish this topic, we should talk about one more thing: "success/failure
229
+ blocks". Let's suppose that when we update our project, in case of failure, we
230
+ want to redirect to the project url instead of re-rendering the edit template.
231
+
232
+ Our first attempt to do this would be:
233
+
234
+ class ProjectsController < InheritedResources::Base
235
+ def update
236
+ update! do |format|
237
+ unless @project.errors.empty? # failure
238
+ format.html { redirect_to project_url(@project) }
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ Looks to verbose, right? We can actually do:
245
+
246
+ class ProjectsController < InheritedResources::Base
247
+ def update
248
+ update! do |success, failure|
249
+ failure.html { redirect_to project_url(@project) }
250
+ end
251
+ end
252
+ end
253
+
254
+ Much better! So explaining everything: when you give a block which expects one
255
+ argument it will be executed in both scenarios: success and failure. But If you
256
+ give a block that expects two arguments, the first will be executed only in
257
+ success scenarios and the second in failure scenarios. You keep everything
258
+ clean and organized inside the same action.
259
+
260
+ == Success and failure scenarios on destroy
261
+
262
+ The destroy action can also fail, this usually happens when you have a
263
+ before_destroy callback in your model which returns false. However, in
264
+ order to tell InheritedResources that it really failed, you need to add
265
+ errors to your model. So your before_destroy callback on the model should
266
+ be something like this:
267
+
268
+ def before_destroy
269
+ if cant_be_destroyed?
270
+ errors.add(:base, "not allowed")
271
+ false
272
+ end
273
+ end
274
+
275
+ == Some DSL
276
+
277
+ For those DSL lovers, InheritedResources won't leave you alone. You can overwrite
278
+ your success/failure blocks straight from your class binding. For it, you just
279
+ need to add a DSL module to your application controller:
280
+
281
+ class ApplicationController < ActionController::Base
282
+ include InheritedResources::DSL
283
+ end
284
+
285
+ And then you can rewrite the last example as:
286
+
287
+ class ProjectsController < InheritedResources::Base
288
+ update! do |success, failure|
289
+ failure.html { redirect_to project_url(@project) }
290
+ end
291
+ end
292
+
293
+ == Belongs to
294
+
295
+ Finally, our Projects are going to get some Tasks. Then you create a
296
+ TasksController and do:
297
+
298
+ class TasksController < InheritedResources::Base
299
+ belongs_to :project
300
+ end
301
+
302
+ belongs_to accepts several options to be able to configure the association.
303
+ For example, if you want urls like /projects/:project_title/tasks, you can
304
+ customize how InheritedResources find your projects:
305
+
306
+ class TasksController < InheritedResources::Base
307
+ belongs_to :project, :finder => :find_by_title!, :param => :project_title
308
+ end
309
+
310
+ It also accepts :route_name, :parent_class and :instance_name as options.
311
+ Check the lib/inherited_resources/class_methods.rb for more.
312
+
313
+ == Nested belongs to
314
+
315
+ Now, our Tasks get some Comments and you need to nest even deeper. Good
316
+ practices says that you should never nest more than two resources, but sometimes
317
+ you have to for security reasons. So this is an example of how you can do it:
318
+
319
+ class CommentsController < InheritedResources::Base
320
+ nested_belongs_to :project, :task
321
+ end
322
+
323
+ If you need to configure any of these belongs to, you can nest them using blocks:
324
+
325
+ class CommentsController < InheritedResources::Base
326
+ belongs_to :project, :finder => :find_by_title!, :param => :project_title do
327
+ belongs_to :task
328
+ end
329
+ end
330
+
331
+ Warning: calling several belongs_to is the same as nesting them:
332
+
333
+ class CommentsConroller < InheritedResources::Base
334
+ belongs_to :project
335
+ belongs_to :task
336
+ end
337
+
338
+ In other words, the code above is the same as calling nested_belongs_to.
339
+
340
+ == Polymorphic belongs to
341
+
342
+ We can go even further. Let's suppose our Projects can now have Files, Messages
343
+ and Tasks, and they are all commentable. In this case, the best solution is to
344
+ use polymorphism:
345
+
346
+ class CommentsController < InheritedResources::Base
347
+ belongs_to :task, :file, :message, :polymorphic => true
348
+ # polymorphic_belongs_to :task, :file, :message
349
+ end
350
+
351
+ You can even use it with nested resources:
352
+
353
+ class CommentsController < InheritedResources::Base
354
+ belongs_to :project do
355
+ belongs_to :task, :file, :message, :polymorphic => true
356
+ end
357
+ end
358
+
359
+ The url in such cases can be:
360
+
361
+ /project/1/task/13/comments
362
+ /project/1/file/11/comments
363
+ /project/1/message/9/comments
364
+
365
+ When using polymorphic associations, you get some free helpers:
366
+
367
+ parent? #=> true
368
+ parent_type #=> :task
369
+ parent_class #=> Task
370
+ parent #=> @task
371
+
372
+ Right now, Inherited Resources is limited and does not allow you
373
+ to have two polymorphic associations nested.
374
+
375
+ == Optional belongs to
376
+
377
+ Later you decide to create a view to show all comments, independent if they belong
378
+ to a task, file or message. You can reuse your polymorphic controller just doing:
379
+
380
+ class ProjectsController < InheritedResources::Base
381
+ belongs_to :task, :file, :message, :optional => true
382
+ # optional_belongs_to :task, :file, :message
383
+ end
384
+
385
+ This will handle all those urls properly:
386
+
387
+ /comment/1
388
+ /tasks/2/comment/5
389
+ /files/10/comment/3
390
+ /messages/13/comment/11
391
+
392
+ This is treated as a special type of polymorphic associations, thus all helpers
393
+ are available. As you expect, when no parent is found, the helpers return:
394
+
395
+ parent? #=> false
396
+ parent_type #=> nil
397
+ parent_class #=> nil
398
+ parent #=> nil
399
+
400
+ == Singletons
401
+
402
+ Now we are going to add manager to projects. We say that Manager is a singleton
403
+ resource because a Project has just one manager. You should declare it as
404
+ has_one (or resource) in your routes.
405
+
406
+ To declare an association as singleton, you just have to give the :singleton
407
+ option.
408
+
409
+ class ManagersController < InheritedResources::Base
410
+ belongs_to :project, :singleton => true
411
+ # singleton_belongs_to :project
412
+ end
413
+
414
+ It will deal with everything again and hide the action :index from you.
415
+
416
+ == URL Helpers
417
+
418
+ When you use InheritedResources it creates some URL helpers.
419
+ And they handle everything for you. :)
420
+
421
+ # /posts/1/comments
422
+ resource_url # => /posts/1/comments/#{@comment.to_param}
423
+ resource_url(comment) # => /posts/1/comments/#{comment.to_param}
424
+ new_resource_url # => /posts/1/comments/new
425
+ edit_resource_url # => /posts/1/comments/#{@comment.to_param}/edit
426
+ edit_resource_url(comment) #=> /posts/1/comments/#{comment.to_param}/edit
427
+ collection_url # => /posts/1/comments
428
+ parent_url # => /posts/1
429
+
430
+ # /projects/1/tasks
431
+ resource_url # => /projects/1/tasks/#{@task.to_param}
432
+ resource_url(task) # => /projects/1/tasks/#{task.to_param}
433
+ new_resource_url # => /projects/1/tasks/new
434
+ edit_resource_url # => /projects/1/tasks/#{@task.to_param}/edit
435
+ edit_resource_url(task) # => /projects/1/tasks/#{task.to_param}/edit
436
+ collection_url # => /projects/1/tasks
437
+ parent_url # => /projects/1
438
+
439
+ # /users
440
+ resource_url # => /users/#{@user.to_param}
441
+ resource_url(user) # => /users/#{user.to_param}
442
+ new_resource_url # => /users/new
443
+ edit_resource_url # => /users/#{@user.to_param}/edit
444
+ edit_resource_url(user) # => /users/#{user.to_param}/edit
445
+ collection_url # => /users
446
+ parent_url # => /
447
+
448
+ Those urls helpers also accepts a hash as options, just as in named routes.
449
+
450
+ # /projects/1/tasks
451
+ collection_url(:page => 1, :limit => 10) #=> /projects/1/tasks?page=1&limit=10
452
+
453
+ In polymorphic cases, you can also give the parent as parameter to collection_url.
454
+
455
+ Another nice thing is that those urls are not guessed during runtime. They are
456
+ all created when your application is loaded (except for polymorphic
457
+ associations, that relies on Rails polymorphic_url).
458
+
459
+ == Bugs and Feedback
460
+
461
+ If you discover any bugs or want to drop a line, join us in the mailing list:
462
+
463
+ http://groups.google.com/group/inherited_resources
464
+
465
+ Copyright (c) 2009 José Valim http://blog.plataformatec.com.br
466
+ See the attached MIT License.