rails3-merb2-win-for-us-number-one
December 27th, 2008
The first WIN for us to come out of the Rails/Merb merge is that the awesome provides API in merb (which Dan gave a compelling introduction to at RailsCamp4), has made its way to rails-3 (along with some tweaks to the API).
This is a win for us because our response_for plugin provides similar functionality (for rails 2 apps). So that's one less plugin for me to maintain.
Can't wait for RARB!
refactorin' VC, 64 stylee
December 1st, 2008
After railscamp#4, I went to #rorosyd's November meetup which experimented with 2 sessions of 6 lightning talk's, and was GREAT (but the lack of time for questions was not so great).
I gave one on refactoring VC (as in MVC) with response_for and inherit_views. For some reason, I had a serious bout of nostalgia as I was preparing the talk and came up with this (watch the for the 'bytes free' automatic countdown). If that pisses you off, then the slides will piss you off slightly less.
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
ardes plugins available github and lighthouse
April 30th, 2008
Many of our plugins are now available on github: github.com/ianwhite
And OSS projects such as these can now be hosted free on lighthouse: ianwhite.lighthouseapp.com
The ones that you see on lighthouse have all had a good dusting off to make sure they're compliant with the very latest edge, and BC to 2.0.2. response_for is now branched to support edge and 2.0.2, but the other plugins haven't yet required this.
If you don't see one you use there, it means that I haven't deemed it being used by many people - so leave a comment to say otherwise
response_for_resources_controller
November 20th, 2007
If you're using response_for, then you might want to grab response_for_resources_controller
It simply replaces the default action modules with ones that use the response_for idiom.
This means that you can override what an action does without overriding how it responds. For example, I want a standard REST controller, that sets a protected attribute, user, on create.
class PostsController < ApplicationController
resources_controller_for :posts
def create
self.resource = new_resource
resource.user = current_user
save_resource
end # the standard response_for :create is used
end
If you don't know what response_for does, it lets you declaratively set the respond_to of an action. For example: if you want to override some aspect of the response, here I just want to change the flash message on successful create:
class LoginsController < ApplicationController
resources_controller_for :logins
response_for :create do |format|
format.html { flash[:notice = "You have logged in" } if resource_saved?
end
end
miscellany
I added save_resource and resource_saved? to rc, as a way of sharing the result of the save outside the scope of the action. Previously I've done this with valid?, but realised that this might hit the database unnecessarily in some circumstances. You can use either idiom.
introducing response_for: decorate your actions with respond_to blocks
October 23rd, 2007
If you've ever subclassed a controller to deal with a particular MIME type, say FBML, you will have had the painful experience of having to copy and paste all of your contoller's actions, adding, or replacing the fbml blocks into the code.
This doesn't feel right - and it isn't. If you have to change some controller logic - you have to remember to change it in all these subclassed controllers.
Enter response_for (rdoc here).
response_for lets you declaratively specify how your actions should respond to particular Mime types. This helps particularly when your action's logic is not simple (like a CRUD action - but it still helps there). Here's an example I just made up:
class ReportController < ApplicationController
# this puppy gets some models to collaborate to form a report
def report
@report = {}
@report[:invoiced] = Customer.find_invoiced
@report[:outstanding] = @report[:invoiced].select {|c| c.latest_invoice.outstanding? }
respond_to do |format|
format.html
format.xml { # some funky xml stuff here }
end
end
end
Let's say I wanted to make FBML responding version of this controller, normally, I'd have to copy/paste this action and add the fbml responses. With response_for, I do this:
class FacebookReportController < ReportController
response_for :report do |format|
format.fbml { # my funky fbml stuff here, accessing @report}
end
end
So, down the line, if I change the way my @report object is generated, I don't have to change my subclassed controllers.
Another example: create
You can choose to replace the respond_to block of the action as well. This means that common actions can be written, and the response can be adjusted according to your needs (resources_controller comes to mind).
Here's a standard create action:
class ForumsController < ApplicationController
def create
@forum = Forum.new(params[:forum])
@forum.save
respond_to do |format|
if @forum.valid?
flash[:notice] = 'Forum was successfully created.'
format.html { redirect_to(@forum) }
format.xml { render :xml => @forum, :status => :created, :location => @forum }
else
format.html { render :action => "new" }
format.xml { render :xml => @forum.errors, :status => :unprocessable_entity }
end
end
end
end
Let's say you want to create an FBML only version of this:
class FacebookForumsController < ForumsController
response_for :create, :replace => true do |format|
if @forum.valid?
format.fbml { # funky fbml stuff for valid record }
else
format.fbml { # and for an invalid record}
end
end
end
One last example
For many actions, you just want to tell your action to respond to particular MIME type and a template does the rest:
class FacebookUsersController < UsersController response_for :index, :show, :types => [:fbml] end
You don't need to have a respond_to block defined in your actions - the parent controller could look like:
class UsersController < ApplicationController
def index
@users = User.find :all
end
def show
@user = User.find params[:id]
end
end
resources_controller + response_for
For those users of resources_controller, response_for gives you an easy way of adding responses on an ad-hoc basis (like if only one or two of your actions need to be RSS feeds or summat)
This is the first release...
and I welcome any bug reports