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.
To fix this, I wrote my own messages structure.
Table of Contents
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.
<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:
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.
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:
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:
<%= 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):
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.