Luke Melia

software dev

February 25, 2007

Cleaning up respond_to blocks with lambda

Whenever Trotter presents at nyc.rb, I always pick up some new ruby idea that I can put to use immediately. His most recent presentation, "Refactoring teh Rails" [sic], was no exception.

Let's start with the end result. ProjectsController#index in Tracks before applying the idea:

RUBY:
  1. def index
  2.     respond_to do |format|
  3.       format.html do
  4.         init_project_hidden_todo_counts
  5.         @page_title = "TRACKS::List Projects"
  6.         render
  7.       end
  8.       format.xml { render :xml => @projects.to_xml( :except => :user_id )  }
  9.       format.rss do
  10.         render_rss_feed_for @projects, :feed => Project.feed_options(@user),
  11.                                        :item => { :description => lambda { |p| p.summary(count_undone_todos(p)) } }
  12.       end
  13.       format.atom do
  14.         render_atom_feed_for @projects, :feed => Project.feed_options(@user),
  15.                                         :item => { :description => lambda { |p| p.summary(count_undone_todos(p)) },
  16.                                                    :author => lambda { |p| nil } }
  17.       end
  18.       format.text do
  19.         render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
  20.       end
  21.     end
  22.   end

There's nothing terribly wrong with this method except for the fact that it is a little long and is difficult to read because of the nested blocks. Trotter suggested thinking of the main CRUD methods in your Rails controllers as dispatchers. I had independently reached the same conclusion -- the answer is Extract Method. But because my Ruby skills are still developing, I couldn't figure out how to do it. After the presentation, I understood it. Here's the after code:

RUBY:
  1. def index
  2.     respond_to do |format|
  3.       format.html  &render_projects_html
  4.       format.xml   { render :xml => @projects.to_xml( :except => :user_id )  }
  5.       format.rss   &render_rss_feed
  6.       format.atom  &render_atom_feed
  7.       format.text  &render_text_feed
  8.     end
  9.   end
  10.  
  11.   def render_projects_html
  12.     lambda do
  13.       init_project_hidden_todo_counts
  14.       @page_title = "TRACKS::List Projects"
  15.       render
  16.     end
  17.   end
  18.  
  19.   def render_rss_feed
  20.     lambda do
  21.       render_rss_feed_for @projects, :feed => Project.feed_options(@user),
  22.                                      :item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) } }
  23.     end
  24.   end
  25.  
  26.   def render_atom_feed
  27.     lambda do
  28.       render_atom_feed_for @projects, :feed => Project.feed_options(@user),
  29.                                       :item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) },
  30.                                                  :author => lambda { |p| nil } }
  31.     end
  32.   end
  33.  
  34.   def render_text_feed
  35.     lambda do
  36.       init_project_hidden_todo_counts(['project'])
  37.       render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
  38.     end
  39.   end

The key is this lambda keyword. In Ruby (and other languages), lambda lets you define a block -- it might help to think of it as an anonymous method body. What you get back is a Proc object, which you can pass around and treat like any other reference. To get it to work as an argument to a method expecting a block, throw an ampersand on the front of the variable name. Eli Bendersky explains all about Ruby blocks and procs better than I can.

Simple but satisfying. While I was passing on the leg of lamb at dinner last night (I don't eat most animals), I couldn't help but think "lambda... yummy."

Leave a Reply

LukeMelia.com created 1999. ··· Luke Melia created 1976. ··· Live With Passion!