Tip: Overriding link_to to accept a block
You’d think I would get tired of saying this: Ruby is amazing! On numerous occasions in recent weeks, I’ve needed some complex logic or multiple lines worth of code to determine the title for my link_to calls. For example:
<%= link_to(course.full? ?
"<span class="full">#{course.name}</span>" :
course.name + course.almost_full? ?
"<span class=\"available\">" +
"(#{pluralize course.capacity, 'spot'} available)</span>" :
"<span class=\"enrolled\">" +
"(#{pluralize course.enrolled.count, 'student'} enrolled)</span>",
:action => 'show', :id => course)
%>
The output of this would be something like:
<a href="/courses/show/1"><span class="full">Cognitive Ergonomics</span></a>
or
<a href="/courses/show/1">Cognitive Ergonomics <span class="enrolled">(3 students enrolled</span>)</a>
or
<a href="/courses/show/1">Cognitive Ergonomics <span class="available">(3 spots available</span>)</a>
...depending on the conditions.
Now, one could argue that I should just break it out into a bunch of if/else branches, and call link_to for each one, but…this is Ruby, there’s got to be a way to keep it DRY! Especially if my link_to arguments are lengthy, which is often the case with link_to_remote.
The solution I came up with today involves adding the following to one of your view helpers, like ApplicationHelper:
def link_to(*args, &block)
if block_given?
concat super(capture(&block), *args), block.binding
else
super(*args)
end
end
As Bruce Williams pointed out to me, due to the way modules are mixed in, calling super on an overridden method will end up calling the original class method. So, if a block is passed, it will capture the result of the block and pass it on as the first argument along with the other arguments to the original link_to method. If no block is given, it will simply pass on all the arguments to the original link_to method
The result is that now I can continue to use link_to in the traditional fashion, or for those tricky situations, just pass a block to it, like:
<% link_to(:action => 'show', :id => course) do %>
<% if course.full? -%>
<span class="full"><%= course.name %></span>
<% else -%>
<%= course.name %>
<% if course.almost_full? %>
<span class="available">
(<%= pluralize course.capacity, 'spot' %> available)
</span>
<% else %>
<span class="enrolled">
(<%= pluralize course.enrolled.count, 'student' %> enrolled)
</span>
<% end %>
<% end -%>
<% end %>
Now I can do all the branching that I want with if/else statements, and I only have to declare my link_to parameters once
If only I could figure out how to alias this method in the helper, it could work with all the variations of the link_to helpers (well, technically, any helper that you want to pass a block in as the first argument). Calling alias_method :link_to_remote, :link_to doesn’t work, and define_method doesn’t seem to let me get the block. Anyone have any ideas?








6 comments
August 20, 2006 at 08:46 AM
October 02, 2006 at 02:45 AM
October 02, 2006 at 02:50 AM
I don’t really understand the usefulness. Surely a simple if/else/elsif statement would suffice. It would certainly be an improvement over the nested if/else statements used.
January 15, 2007 at 01:36 AM
Passing a block to link_to is cool, but there’s other ways. You could render the string and then pass it to link_to, or my favorite, use ternary operators and multiple lines until you make even perl programmer’s eyes bleed.
June 19, 2007 at 06:29 PM
What about this solution? http://nullstyle.com/2007/6/20/link_to-as-a-block-helper
July 30, 2007 at 09:24 AM
Speak your mind: