Updating response_for for edge rails
September 23rd, 2008
Rails edge is getting lots of changes under the hood, consequently garlic has been kicking up a fuss for response_for and inherit_views...
So, I've rewritten them! Both plugins are now a lot cleaner, and have less LOC, due to some great changes under the actionpack hood.
inherit_views | response_for
Summary: response_for lets you specify a default response for an action. If that action (or a before_filter) doesn't perform a render or redirect, then the default response is rendered. You can also 'stack' responses - the latter ones override the former ones.
To get the full benefit of response_for, you need to write your actions with it in mind. It's not allways appropriate - but for some things it's great. if you have a set of actions that you use a lot (say if you're a resources_controller user), or if you use superclass controllers, then you'll probably get some benefits form response_for.
Before talking about the rewrite, I'll just give a before and after example of a common use case for response_for - subclassed controller action. If you want to skip this just head straight to the section regarding the rewrite.
Before
Here is a fairly standard controller with an action that does some stuff, then conditionally renders a response.
class SuperclassController < ApplicationController
# the action does some stuff, then conditionally renders a repsonse
# for :html, and :js
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
There are two main reasons to override this action in a subclassed controller, (i) to change the response, and (ii) to change the logic. Both of these require a rewrite of the entire action! This is painful.
Code speaks louder than words, so here are two subclassed controllers, both of which add authentication, one which chnages the html response on success, the other which changes the logic. Click here to skip the code.
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
# We change the response
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, but we have to write out the response again
did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
After..
Here's the same effect, with response for. We do need to write the superclass action a little differently first. We just take the response out of the action. We need to add an instance variable, so the response can conditionally render different things.
class SuperclassController < ApplicationController
# this time with response_for
def the_action
do_some_stuff
@did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
end
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
Now, our two subclassed controllers look like this:
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
# we only override the repsonse that has changed
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, we don't need to touch the response
@did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
end
end
The rewrite
The original response_for had to do some pretty tricky messin about to kick in once the action had done its thing. Things got a whole lot easier when actionpack introduced the default_render hook.
default_render is called after your action if a response has not explicitly been rendered (or redirected). You can use this yourself in your controllers if you want to do something other than just rendering the default template. response_for alias_method_chains it to render any declared responses..
If you're a previous response_for user
Along with the rewrite, I changed the way repsonse_for works. It used to intercept respond_to, so that you could override any repsonse declared in your action. This resulted in a bunch of weird edge cases, and so I decided to ditch it (read more about this at the end of the response_for README). If you need the old functionality use the 0.1-stable branch.
inherit_views | response_for
2 Responses to “Updating response_for for edge rails”
Sorry, comments are closed for this article.
Rasheed Abdul-Aziz Says:
Just a little note that you included Subclass2Controller twice, where in the second instance i believe you want to show us the modified superclass?September 23rd, 2008 at 04:06 AM
Ian White Says:
Rasheed, thanks for that - fixing it now!September 23rd, 2008 at 06:17 AM