opensoul.org

Tip: Overriding link_to to accept a block

August 4, 2006 rails, ruby

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?

I am Brandon Keepers. I build Internet things, usually with Ruby or JavaScript. I work at GitHub and live in Holland, MI.