opensoul.org

Tip: Overriding link_to to accept a block

August 4, 2006 code 4 min read

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?

This content is open source. Suggest Improvements.

@bkeepers

avatar of Brandon Keepers I am Brandon Keepers, and I work at GitHub on making Open Source more approachable, effective, and ubiquitous. I tend to think like an engineer, work like an artist, dream like an astronaut, love like a human, and sleep like a baby.