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:
-
def index
-
respond_to do |format|
-
format.html do
-
init_project_hidden_todo_counts
-
@page_title = "TRACKS::List Projects"
-
render
-
end
-
format.xml { render :xml => @projects.to_xml( :except => :user_id ) }
-
format.rss do
-
render_rss_feed_for @projects, :feed => Project.feed_options(@user),
-
:item => { :description => lambda { |p| p.summary(count_undone_todos(p)) } }
-
end
-
format.atom do
-
render_atom_feed_for @projects, :feed => Project.feed_options(@user),
-
:item => { :description => lambda { |p| p.summary(count_undone_todos(p)) },
-
:author => lambda { |p| nil } }
-
end
-
format.text do
-
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
-
end
-
end
-
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:
-
def index
-
respond_to do |format|
-
format.html &render_projects_html
-
format.xml { render :xml => @projects.to_xml( :except => :user_id ) }
-
format.rss &render_rss_feed
-
format.atom &render_atom_feed
-
format.text &render_text_feed
-
end
-
end
-
-
def render_projects_html
-
lambda do
-
init_project_hidden_todo_counts
-
@page_title = "TRACKS::List Projects"
-
render
-
end
-
end
-
-
def render_rss_feed
-
lambda do
-
render_rss_feed_for @projects, :feed => Project.feed_options(@user),
-
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) } }
-
end
-
end
-
-
def render_atom_feed
-
lambda do
-
render_atom_feed_for @projects, :feed => Project.feed_options(@user),
-
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) },
-
:author => lambda { |p| nil } }
-
end
-
end
-
-
def render_text_feed
-
lambda do
-
init_project_hidden_todo_counts(['project'])
-
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
-
end
-
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."
