Span Tags for error_message_on in Ruby on Rails

by Jarrett Colby

error_message_on gives you a flexible way to display validation errors, but unfortunately, it assumes that you want the messages printed inside <div> tags. A quick patch replaces the <div>s with <span>s, which are valid in a broader range of contexts.

We'll be implementing this as a plugin. Since we only want to change a <div>s to a <span>, we could probably get away with using alias_method_chain. But that would involve modifying the output with some regex trickery. That approach always makes me nervous—if in some cases the output is slightly different from what I expected, the regex will crash and burn.

So instead, we'll just rewrite the method. Don't worry, it's quite short. Here's the code:

module SpanErrorMessages
        def self.included(base)
                ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
                                %(<span class="fieldWithErrors">#{html_tag}</span>)
                end
        end
        
        # Old method signature:
        # def error_message_on(object, method, prepend_text = "",
        #     append_text = "", css_class = "formError"}
        
        # Use the new method like this:
        # error_message_on(
        #   @employee,
        #   :email,
        #   :css_class => 'really_bad_error',
        #   :tag => 'p'
        # )
        def error_message_on(object, method, *args)
                options = {:tag => 'span'}.merge(args.extract_options!.symbolize_keys)
                prepend_text = args[0] || options[:prepend_text] || 
                append_text =  args[1] || options[:append_text]  || 
                css_class =    args[2] || options[:css_class]    || 'formError'
                if (
                        obj = (object.respond_to?(:errors) ?
                        object :
                        instance_variable_get("@#{object}"))
                ) && (errors = obj.errors.on(method))
                        content_tag(options[:tag], "#{prepend_text}#{errors.is_a?(Array) ?
                                errors.first : errors}#{append_text}", :class => css_class)
                else 
                        ''
                end
        end
end

Notice that we've changed the method signature. It now supports a variable number of arguments, allowing for future expansion and a final options hash. Plus it's backwards-compatible. By the way, if you haven't read about Array#extract_options!, you should. It's handy for when you have an *args parameter that can take one or more hashes at the end.

Anyhow, the options hash accepts four keys: :prepend_text, :append_text, :css_class, and :tag. Three of those used to be the optional parameters, and you can still use them that way, but I like having the choice to put them in a hash instead. It permits you to specify the css_class without filling in the default values for prepend_text and append_text.

If you're experienced with Rails plugins, you can probably figure out what to do from here. If not, you should read The Complete Guide to Rails Plugins: Part I by Geoffrey Grosenbach. Then, when you're ready to make your plugin, paste the above code into the following file:

vendor/plugins/span_error_messages/lib/span_error_messages.rb

Now create another file at:

vendor/plugins/span_error_messages/init.rb

Paste this into it:

ActionView::Base.send :include, SpanErrorMessages

Don't forget to restart your server (even if you're in development mode).