Creating reusable flashes in Rails using Shoelace
Purpose
To document how I made a reusable flash system leveraging Shoelace + Rails
The Problem
We all like beautiful flashes! But sometimes it takes work to make a reusable flash system. I’m going to show how I implemented mine with Shoelace, but this could easily be extended to any other CSS framework or your own CSS.
Building a reusable interface
There are 2 steps to building a reusable interface for flashes. The first is for the backend.
Creating a controller concern
Flashes are accessed from a Controller, so as such, its easiest to create a controller concern called: Flashable
In your app/controllers/concerns
directory, create a file called flashable.rb
In your app/controllers/concerns/flashable.rb
file we’re going to add a number of convenience methods to interface with flash.
Basically what we want to do is be able to call things like flash_success("My message")
or flash_danger("Dangerous operation!")
from a controller. So lets first start by building a #show_flash
base.
# app/controllers/concerns/flashable.rb
module Flashable
extend ActiveSupport::Concern
# These are the icons that come from the Shoelace docs on Alert.
ICONS = {
primary: "info-circle",
success: "check2-circle",
info: "gear",
warning: "exclamation-triangle",
danger: "exclamation-octagon"
}.freeze
included do
def show_flash(type, message, now: false, icon: nil)
icon ||= ::Flashable::ICONS[type]
hash = { message: message, icon: icon }
return flash.now[type] = hash if now
flash[type] = hash
end
end
end
This is the building block for our convenience methods. Our convenience methods will build off the structure we have in place here.
Our final result will look like this:
# app/controllers/concerns/flashable.rb
module Flashable
extend ActiveSupport::Concern
ICONS = {
primary: "info-circle",
success: "check2-circle",
info: "gear",
warning: "exclamation-triangle",
danger: "exclamation-octagon"
}.freeze
included do
def flash_primary(message, now: false, icon: nil)
show_flash(:primary, message, now: now)
end
def flash_success(message, now: false, icon: nil)
show_flash(:success, message, now: now)
end
def flash_info(message, now: false, icon: nil)
show_flash(:success, message, now: now, icon: icon)
end
def flash_warning(message, now: false, icon: nil)
show_flash(:warning, message, now: now, icon: icon)
end
def flash_danger(message, now: false, icon: nil)
show_flash(:danger, message, now: now, icon: icon)
end
def show_flash(type, message, now: false, icon: nil)
icon ||= ::Flashable::ICONS[type]
hash = {message: message, icon: icon}
return flash.now[type] = hash if now
flash[type] = hash
end
end
end
Yes there are some ways we could’ve meta-programmed our flash messages, but sometimes verbosity is okay! So now, wherever the Flashable
module gets included, it will define these methods in the context of the Class or Module that includes it.
The next step is to go into your app/controllers/application_controller.rb
and include Flashable
like so:
# app/controllers/application_helper.rb
class ApplicationController
include Flashable
end
This means every controller now has access to our new flash methods.
Reusable frontend interface
Now that our controller can successfully call flash_success("message")
now we have to create a way for the frontend to interpret the flash hash and render it accordingly.
In your app/helpers/application_helper.rb
we’re going to make the following helper method: #show_alert
This will take in the following parameters: type, message, icon
and will use these 3 arguments to render a shoelace alert.
Heres what the method ends up looking like:
# app/helpers/application_helper.rb
module ApplicationHelper
def show_alert(type:, message:, icon:)
tag.sl_alert(type: type, open: true, closable: true) do
tag.sl_icon(nil, slot: "icon", name: icon) + "\n" + message
end
end
end
Example
In any one of our views we can do call the following code to display a Shoelace flash:
<%= show_alert(type: "success", message: "You have successfully logged in!", icon: ::Flashable::ICONS[:success] %>
And heres what it will look like:
Final piece of the puzzle
Alright now we have a way for displaying flashes, but now we actually need to add a flash handler in our layout.
Go to app/views/layouts/application.html.erb
and we’ll add the flash handler code.
<html>
<head>
</head>
<body>
<div>
<% flash.each do |type, hash| %>
<div style="margin: 1rem 0;"></div>
<%= show_alert(type: type, message: hash[:message], icon: hash[:icon]) %>
<% end %>
</div>
<main>
<%= yield %>
</main>
</body>
</html>
What it all looks like
In a controller of your choice, add some flashes, and then go check out a page! Heres how all the flashes would stack up on the same page!
class HomeController < ApplicationController
def index
%w[primary success info warning danger].each do |str|
flash_method = "flash_#{str}".to_sym
public_send(flash_method, str, now: true)
end
end
end