Haml (4.0) in your helpers!

21 Jun 2013

Every once in a while you want to DRY up your views by moving the information into your helpers. Makes sense, right? :) Well when you want to do it with ERB's content_tag it gets tricksy. So you might naturally do it with Haml. Why not throw in a little white space indentation to get rid of those closing tags.

Haml in our Helpers

This is where it gets interesting. For this demo we're going to move the explain errors block into the helper, but we're going to put it in its own helper module that you can include into ApplicationHelper or the place of your choice.

New Helper

# just an fyi to the next guy that we're using haml in this module
require 'haml'

module FeedbackHelper
  # for kicks we throw in an extend the important part is the include
  def self.included(base)
    base.class_eval do
      include Haml::Helpers
      extend Haml::Helpers
    end
  end

  ...

  def explain_errors( model )
    # this call is key. it allows our haml methods to be called
    # outside of ActionView.
    # Update: Note that you don't always need this line. i.e. in Rails
    init_haml_helpers

    # now the meat of the method
    if model.present? && model.errors.any?
      capture_haml do
        haml_tag :h4 do
          haml_concat <<-HERE_DOC
            #{pluralize( model.errors.count, "error")} prohibited this
            #{ model.class } from being saved:
          HERE_DOC
        end
        haml_tag :ul do
          model.errors.full_messages.each do |msg|
            haml_tag :li, msg
          end
        end
      end
    end
  end
  ...
end

Now to parse the errors block at the top of our forms all we need to do is this... (note that this is haml syntax)

 = simple_form_for @object do |f|
  = explain_errors @object
  ...

That's it! Now go forth and DRY up those views!

UPDATE 2013-07-18

The above works sometimes and not other times. In the mean time here is an ERB variant that can do the same thing. :)

  module FeedbackHelper
    ...

      def explain_errors(model)
        if model.present? && model.errors.any?
          html = ""
          html << content_tag(:h4) do
            <<-HERE_DOC
              #{pluralize( model.errors.count, "error")} prohibited this
              #{ model.class } from being saved:
            HERE_DOC
          end
          html << content_tag(:ul) do
            model.errors.full_messages.each {|msg|
              concat(content_tag(:li, msg, class: 'text-error'))
            }
          end
          html.html_safe
        end
      end

    ...
  end

Note that this will add a text-error class to the error li item is a Twitter Bootstrap DOM hook.

Note this blog post was what helped me write this helper method. Nested Content Tags in Rails 3

David Southard

a ruby guy

Contact Info: