Problem

Your view code in your Ruby on Rails app is getting messy with duplicate code and ugly conditional statements. For example, maybe you have something in your view that displays the user’s avatar (that is, if the user has uploaded one already) such as:

<% if current_user.avatar %>
    <%= image_tag(current_user.avatar.public_filename(:thumb) %>
<% else %>
    <%= image_tag("avatar_thumb.png") %>
<% end %>

Solution

Create custom helper tags. An easy way to get into this is to look at the source of built in helpers and piggy back on them. Don’t get too scared, the built in helpers are ones you already use like link_to, image_tag, etc. For this example, let’s build on the image_tag to write a custom “avatar_tag” helper.

With this call from the view we want to trigger an “avatar_tag” helper method in our rails_app/helpers/application_helper.rb that we will create. You can also put it in your one of your controller specific helper modules if you don’t want this helper available system wide.

Let’s first look at the “image_tag” helper that is built in to rails:

# File src/rails-1.2.1/actionpack/lib/action_view/helpers/asset_tag_helper.rb, line 185
def image_tag(source, options = {})
  options.symbolize_keys!

  options[:src] = image_path(source)
  options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize

  if options[:size]
      options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
      options.delete(:size)
  end

  tag("img", options)
 end

Let’s dissect this a bit…

  • First, the method is accepting a source location and a hash of HTML options (options={}) that we can pass in to override the default values. (such as :alt, :width, etc)
  • Inside the image_tag method, options.symbolize_keys! is going to turn our options hash into a new hash with all the keys converted to symbols.
  • Next it is going to assign the HTML src option to the source that we pass in.
  • Same goes to any other options if we passed them in to override any defaults. For example, if we called image_tag(“logo.png”, {:alt=>”This is a temp logo”}), it would override the default alt value that image_tag provides (the src of the image).
  • Finally, it is going to use the tag method to create the actual HTML tag.

Now let’s create our own “avatar_tag” helper - which, as we can see, doesn’t look too different:

def avatar_tag(asset, options = {})
        options.symbolize_keys!

        options[:class] ||= :thumb
        options[:alt] ||= (asset) ? asset.public_filename : "No Avatar Found"

        #buddyicon_tiny.png and buddyicon_thumb.png should be in app directory
        options[:src] = (asset) ? asset.public_filename(options[:class]) : "/images/buddyicon_#{options[:class].to_s}.png"

        tag(:img, options)
    end

What’s new here?

  • First of all we are calling our new method “avatar_tag” - which we will then be able to call from any of our views (since we put it in the application helper).
  • We are using the attribute “class” to define the type of avatar to use. We also set it so that it is :thumb by default.
  • For the alt attribute, we set the default to the filename when the source is not nil and set it to “No Avatar Found” if it is nil.
  • Finally, we set the source to be the avatar source if source is not nil and if it is nil we set it to our default avatar image. Note that we are also passing in options[:class] in the filename so we will need to name our default images - avatar_thumb.png and avatar_tiny.png

Now rather than those conditionals clogging up our view template, we can simply render our :tiny version of our user’s avatar icon by calling:

<%= avatar_tag(current_user.avatar, :class=>:tiny) %>

or the :thumb version by just calling:

<%= avatar_tag(current_user.avatar) %>

Mmm..much cleaner…..

Good luck and as always, please post in the comments if you know/found a better way to do this!