Ruby on Rails
HowtoWorkWithHelpers

Introduction

Helpers (“view helpers”) are modules where you can write methods which are automatically usable in the associated view. What’s the associated view? If your view is, say, app/views/user/prefs.rhtml, then the helper you’re after is UserHelper, found in app/helpers/user_helper.rb.

The purpose of writing a method in a helper is to simplify the view. It’s best if the view file (RHTML/RXML) is short and sweet, so you can see the structure of the output. Nitty-gritty details are best left to helper methods and partials, where they can be parametized and used repeatedly.

Note Remember that helpers can’t be used in the controller! If you need a method both in a view and a controller, define them in the controller and export them
via helper_method() .

Q: Can helpers be used in models ?

Example

For example, in the view (views/user/books.rhtml):


  You have <%= @books.size %> books in your collection:
  <ul>
    <%= book_list @books, :max => 5 %>
  </ul>

In the helper (helpers/user_helper.rb):


  def book_list(books, options={:max => 10})
    max = options[:max]
    list = books[0...max].map { |book|
      link = link_to "#{book.title}", :action => 'book', :id => book.id
      "<li>#{link}</li>" 
    }
    # Add a "more..." link if the list is at maximum size.
    if list.size == max
      more_link = link_to "more...", :action => 'books_all'
      list[-1] = "<li>#{more_link}</li>" 
    end

    return list # this one was missing here AFAIK

  end

PS: You could also use TagHelper for the li-wrapping.

And the result:

  You have 2 books in your collection:
   * Stone Alone
   * Catch-22

or…

  You have 37 books in your collection:
  * Catcher in the Rye
  * Fool's Gold
  * VB.NET Unleashed!
  * Harry the Houndog
  * more...

And there you have it. Ensuring the list only displays so many items, and making the last item a special link, is not trivial work, and that logic would clutter the view considerably. Helpers are just the tonic for such a situation.

Partials

If the view logic you want to capture in a helper is more HTML-heavy, you can use a partial instead.

Extra Helpers

Final note: each view, as stated above, has one helper module automatically wired up to it. If you are creating so many helper methods that you wish you could separate them somehow, you can:


  class UserController < ApplicationController
    helper :user   # This is implied; you don't have to write this.
    helper :extra_user
  end

Your user view can now draw on the methods of two helper modules, and you can add more if you like. They share the same namespace, though, so don’t define the same method in both.

The additional helper we specified in the above controller is ExtraUserHelper, defined in app/helpers/extra_user_helper.rb.

Comment: Interesting. I would never have guessed that the name “helper” was meant to refer to views, rather than code in general. What was the rationale for putting the helper directory at the same elvel as controllers and models, rather than inside the view directory where its purpose would be more readily clear?

Use tag and content_tag to avoid hard coding HTML

You are recommended to use the tag and content_tag helpers in helpers instead of plain html emitting:

Example

with hardcoded-html


def collapsible_fieldset(options = {}, &block)
  html = '<fieldset>'
  html << capture(&block) if block_given?
  html << '</fieldset>'
  Binding.of_caller do |binding|
    concat(html, binding)
  end
end

using content_tag


def collapsible_fieldset(options = {}, &block)
  html = content_tag :fieldset, (capture(&block) if block_given?)
  Binding.of_caller do |binding|
    concat(html, binding)
  end
end

Be aware that you will get the view output duplicated if you call the concat method in a helper which is called from <%= %> tags. In the above example you should remove the equals sign and allow concat to output the view.

Note that in ruby 1.8.5 binding_of_caller does not work. You may use the block’s caller instead:


def collapsible_fieldset(options = {}, &block)
  return unless block_given?
  html = content_tag :fieldset, capture(&block)
  concat( html, block.binding)
end

See also: Old dead (link removed) is partially available at http://www.planetrubyonrails.org/show/feed/164

Category: Howto