extended_inherited_resources 0.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.
@@ -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.