Creating Multiple Flash Messages in Ruby on Rails

On my Flight Historian application, a number of my pages make use of the flash and flash.now session messages capability for errors, warnings, successes, and informational messages. However, some of those pages needed to have multiple messages of the same type (e.g., multiple warnings), which flash didn’t allow me to do. Additionally, I had some views that were generating status messages of their own (for example, if a collection was empty on a page that had multiple collections), and so I ended up with several ways to generate messages that didn’t output consistent HTML.

Top of a Flight Historian show trip page, with two inconsistently formatted messages. One reads 'Successfully updated trip' and the other reads 'This trip is hidden.'

Flight map generated using the Great Circle Mapper—copyright © Karl L. Swartz

To fix this, I wrote my own messages structure.

Message Partial

I first created a message partial which would render an individual message banner:

This partial accepts a message type (error, warning, etc.) which is used to select the CSS class, and the message text.

app/views/layouts/_message.html.erb
<div class="message message-<%= type %>"><%= text.html_safe %></div>

render_message

In order to shorten the partial call, I wrote a render_message(type, text) method in the application helper to call the partial:

app/helpers/application_helper.rb
def render_message(type, text)
  render partial: "layouts/message", locals: {type: type, text: text}
end

If necessary, I can call this directly from the view to insert a message anywhere I want. However, I also need to be able to have controllers add messages to be displayed at the top of the page.

add_message

I created an add_message(type, text) method in the application controller which creates an array of message hashes (if one doesn’t already exist) and allows me to add messages to it.

app/controllers/application_controller.rb
def add_message(type, text)
  @messages ||= []
  @messages.push({type: type, text: text})
end

render_messages

Now, I need to display all messages. Back in the application helper:

app/helpers/application_helper.rb
def render_messages
  order = [:error, :warning, :success, :info]
  @messages ||= []
  @messages.concat(flash.map{|k,v| {type: k.to_sym, text: v}}) if flash
  @messages.sort_by{|m| order.index(m[:type]) || order.length}
    .map{|m| render_message(m[:type], m[:text]) }.join.html_safe
end

Since my messages can have different priorities, when I display a page with multiple messages, I want them sorted with the highest priorities first. My order array defines the order I want my types sorted in.

Next, I make sure a @messages array exists, and then I merge any flash messages into my messages array, so they display the same as any other message. For each flash, the hash’s key is used as the type.

Then, I get the index of each message type in my order array, and sort on that. If a message has a type that’s not in the array, it’s given an order number of the length of the array. Since that number will always be higher than any array index, the unknown message types will be sorted below the defined message types.

Finally, I loop through the messages with map, call render_message to render the message partial for each, and then join all the partials together. Since this is the last operation of the method, this string of joined message partials will be returned by render_messages.

Application Layout

Finally, I call render_message at the top of the page in my application layout:

app/views/layouts/application.html.erb
<%= render_messages %>

The end result works well. Here, I’m explicitly mixing a status message from my controller (yellow) with a flash message from a database update (green):

Top the same Flight Historian show trip page, with two consistently formatted messages. One reads 'This trip is hidden' and the other reads 'Successfully updated trip.'

Flight map generated using the Great Circle Mapper—copyright © Karl L. Swartz

And that’s it! I wish the flash itself supported having multiple messages of the same type, but since it doesn’t, this works well for me.

Tags: