Ian White

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

Ian White

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.

Ian White

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

Ian White

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

Ian White

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.

Ian White

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