Rails View Helpers

posted 12 years ago

Too often repetitive markup is necessary to display things correctly. For example, if you use horizontal forms from the twitter bootstrap framework, here is the markup for typical email and password inputs:

<div class="control-group">
    <label class="control-label" for="inputEmail">Email</label>
    <div class="controls">
        <input type="text" id="inputEmail" placeholder="Email">
    </div>
</div>
<div class="control-group">
    <label class="control-label" for="inputPassword">Password</label>
    <div class="controls">
        <input type="password" id="inputPassword" placeholder="Password">
    </div>
</div>

Wouldn't it be a lot nicer to call some method from the view to generate that instead of writing it each time?

<!-- Something like the following -->
<%=  bootstrap_input :email %>
<%=  bootstrap_input :password %>

If your answer is "Yes, it would!", then read on...

Setup

All helper methods used in views should be defined in the helpers directory of the app. So create a new helper and call it something meaningful.

# app/helpers/form_helper.rb

module FormHelper

end

And make sure to declare it as a helper in your controller as well. If you'll need it in many controllers, it's fine to add it in the application controller.

# app/controllers/application_controller.rb
...

  helper FormHelper

...

Ruby basics used below

Skip this if you're good at ruby coding.

There are some useful things to do when writing methods. One of them is setting a variable if one is not defined. This is useful for setting default values in options hashes.

hash = {}
hash[:pizza]
# => nil

hash[:pizza] ||= "Cheese"
hash[:pizza] 
# => "Cheese"

hash[:pizza] ||= "Pepparoni"
hash[:pizza]
# => "Cheese"

Another basic concept is symbol-to-proc, which basically allows you to pass in a symbol instead of a block to methods that take a block, and it will apply the method the symbol represents instead.

[1, 2, 3].reduce &:+
# => 6

# instead of
[1, 2, 3].reduce{ |sum, val| sum += val }
# => 6

The last helpful method (added by Rails) is Hash#slice. Basically it gets a subset of a hash based on keys.

hash = {a: 1, b: 2, c: 3}
hash.slice :a, :b
# => {a: 1, b: 2}

Notes on Rails helpers

Rails comes with a number of helpers to help you make markup abstractions. For instance, link_to creates anchor tags. The kind of end-all-be-all helper is content_tag.

Helpers generally accept a value or a block and an options hash. It would be wise to emulate that approach. Remember, to pass a block right down through the function, pass the block itself. For example wrapping something in a 'wrapper' div:

def div_with_wrapper(options={}, &block)
  content_tag :div, class: 'wrapper', do
    content_tag :div, options, &block
  end
end

And using it

<%=  div_with_wrapper, class: 'inner' do %>
  <p>Normal markup can go in here!</p>
<% end %>

If you want to put multiple tags inside another one, then you need to concatenate them. Note also that you cannot use Array#join or the markup will be escaped.

def my_helper
  list = ['apple', 'orange', 'pear']
  content_tag :ul do
    list.map{ |fruit| content_tag :li, fruit }.reduce &:+
  end
end

def another_helper
  first = content_tag :p, "First"
  content_tag :div, first + content_tag(:p, "Second")
end

Also, if you want to use raw text instead of helpers (not necessarily encouraged), then know about heredocs which allow multiple-line text with string interpolation, and the html_safe method that turns your string (not trusted by rails views and therefore encoded) into an ActiveSupport::SafeBuffer which will be rendered as html.

def span_with_value(value)
  markup = <<HTML
    <span>
        #{value}
    </span>
HTML
  markup.html_safe
end

Final note: if you want to see the actual markup as text in your view (for debugging), the normal html_escape (aka 'h') method will not work, and calling to_s on the ActiveSupport::SafeBuffer just returns itself. Instead, concatenate to an empty string.

<%= "" + my_helper_method %>

Writing a helper method

Now it's time to write the convenient bootstrap form method in FormHelper.

module FormHelper

  def bootstrap_input(field, options={})
    options[:id] ||= "input#{field.to_s.camelize}"
    options[:label] ||= field.to_s.titleize
    options[:type] ||= inferred_type_of field
    options[:placeholder] ||= options[:label]
    
    controls = content_tag :div, class: 'controls' do
      tag :input, options.slice(:type, :id, :placeholder)
    end
    content_tag :div, class: 'control-group' do
      content_tag(:label, options[:label], class: 'control-label', 
            for: options[:id]) + controls
    end
  end
  
  private
  
    def inferred_type_of(field)
      if field == :password
        'password'
      else
        'text'
      end
    end

end

And when we call the following in the view:

<%=  bootstrap_input :email %>
<%=  bootstrap_input :password %>

We get:

<div class="control-group">
    <label class="control-label" for="inputEmail">Email</label>
    <div class="controls">
        <input id="inputEmail" placeholder="Email" type="text" />
    </div>
</div>
<div class="control-group">
    <label class="control-label" for="inputPassword">Password</label>
    <div class="controls">
        <input id="inputPassword" placeholder="Password" type="password" />
    </div>
</div> 

Conclusion

Why is this better than just markup? Well for one thing it is more abstraction and less writing code which makes programming more fun and code more readable. Just look at the code above. Would you rather write an interesting function and call it twice or slog through the markup?

More importantly, if you want to change something, it's easy to find. Suppose we want to make sure all of our 'bootstrap inputs' have the type "email" instead of "text" when they are an email field? We simply change the code in one place:

def inferred_type_of(field)
  if [:email, :password].include? field
    field.to_s
  else
    'text'
  end
end

On a final note, don't overuse helpers. They are great for cleaning up very structured and repetitive markup, but if you find yourself passing in tons of arguments and writing lots of conditionals, it may be time to nest helpers, pass in blocks of markup, or just use static markup.

For example if I needed 10 if statements in inferred_type_of, it might be better just to pass in :type in the options hash. If even worse I decide I want to change the width, color, and wrap half of the inputs in an absolutely positioned div, using a helper may only complicate things.