Useful Flash Messages in Rails

11 July 2008

Useful Flash Messages in Rails

Inevitably somewhere in your rails app you display flash messages to your users to inform them that an action has (or hasn't) taken place. Sometimes they need to provide more info, maybe you've just created some new information for them. Why make them sit around, or wonder where they need to go next when you can take them straight to it in the message itself?

I've ripped this code out of a project I've been working on and made it a little more generic. Initially I was going to just insert a link in the flash message, but as I was rightly pointed out by my pair and elegant coder extraordinaire, that would mean I'd be polluting my controller with logic and content that should be handled by the view. How do we solve a problem like pollution?

First, we updated the controller so that the flash message now looked something like this:

flash[:error] = "Username and password do not match. If you have 
                   forgotten your password you can %s"

The %s is there to use a string substitution later. If you wanted to insert your link somewhere else, move the %s. Next, we created a new key in flash to hold our additional information (the link path and text):

flash[:error_item] = ["reset it here", forgot_path]

Quite simply, it's an array with the first item being the text and the second the path. Now for handling it within our view, here's what I've got in application_helper.rb:

FLASH_NOTICE_KEYS = [:error, :notice, :warning]

  def flash_messages
    return unless messages = flash.keys.select{|k| FLASH_NOTICE_KEYS.include?(k)}
    formatted_messages = messages.map do |type|      
      content_tag :div, :class => type.to_s do
        message_for_item(flash[type], flash["#{type}_item".to_sym])
      end
    end
    formatted_messages.join
  end

  def message_for_item(message, item = nil)
    if item.is_a?(Array)
      message % link_to(*item)
    else
      message % item
    end
  end

The reason for the constant is that I don't use just :error and :notice, I've also got :warning for times when something needs action but not because of something the user has done wrong (I don't want to make them feel bad when they've not had the opportunity to be good!). In flash messages I exit out on the first line if we don't have content to process for the flashes I expect to output (:error, :notice, and :warning) otherwise I loop over each and create a div to contain the message. The class on the div will be either error, notice or warning so you can style it properly. We defer working out the content to message_for_item

In message_for_item I check if the input is an Array, and if so I use the build in string substitution method to insert the link in the relevant place. As you may notice, I've called splat on item by putting an asterisk in front of it. That means your array can essentially just be the parameters you'd pass in to link_to, allowing you to add additional classes or options if you wish. If it's not an array, I skip the link to and just to the substitution. This will handle traditional flash messages as well as some that might want substitution of a string or other object. Wherever you'd previously inserted your flash messages, now you call:

and the end result is something like this:

So what else could you do with this? Well we extended it to be a little more intelligent based on the object that was passed in. You could also use it as part of a before filter if you've got flash messages that are relatively similar across actions on controllers. Just put %s in the bit to substitute, and then at the relevant part of your action you can insert the a string into flash[:error_item] or the appropriate key, and have it displayed. Like this:

before_filter :set_message, :only => [:create, :update]

  def create
    @order = Order.create!(params[:order])
    flash[:notice_item] = "#{@order.items} items" 
  end

  def update
    @order = Order.find(params[:id])
    @order.update_attributes(params[:order])
    flash[:notice_item] = "#{@order.items} items" 
  end

  private
    def set_message

      flash[:notice] = "Thank you, we have updated your order with %s" 
    end

I hope it helps. Coming up next, my sexy form builder!

I'm putting together a weekly newsletter to help developers spend more time on what they do best. You should sign up to Cloud Services Weekly  today!
Filed under Software Dev