Add custom flash types in Rails

How to Create Custom Flash Types in Rails

While reading the Rails codebase last week, I came across a useful method that lets you create custom flash types. In this post, we'll learn how to use it and also how it's implemented behind the scenes. In the process, we'll also learn a few metaprogramming tricks in Ruby.

4 min read

Sometimes you want to store some data in the Rails Session for the next request; for example, to display an error message or notice to the user after redirecting them to another page.

You can store this message in the session using flash, and access it in the next request. The flash is cleared in the next request.

Most of the time, we want to use the flash for two reasons: to show a notice to the user, or to alert them about something. Hence, out of the box, Rails provides two accessors on the flash object, called notice and alert, which let you create and access the notice and alert messages in the controllers and views respectively.

class FlashController < ApplicationController
  def show
    flash.alert = 'usage limit exceeded!'
    flash.notice = 'the post was successfully saved'
  end
end

It is also possible to assign a flash message when you redirect the request. Simply pass either :notice or :alert option to the redirect_to method and the value will be stored in the flash.

render home_path, notice: "You have successfully signed out."
redirect_to home_path, alert: "Oops! Something went wrong."

Anything you store in the flash is available in the views. You can access the flash both as an object or a Hash.

<%= flash.notice %>

<%= flash[:alert] %>

What's more, you don't even need the flash, as Rails provides the notice and alert helper methods, so you can simply do this:

<%= notice %>

<%= alert %>

What if you want to add a custom flash type?

If you need a different flash type than notice or alert, you can simply treat the flash object as a plain Ruby Hash and register the type as a new key-value pair with the standard Hash syntax.

The following code registers a new type called warning.

class FlashController < ApplicationController
  def show
    flash[:warning] = 'WARNING'
  end
end

Then, in your views, access the warning message as a standard Hash.

<%= flash[:warning] %>

However, there're two drawbacks to this approach.

1) You don't get convenient view helpers like notice and alert. You have to use the Hash accessors in your views.

2) You can't pass the warning option in your redirect_to helper. Instead, you'll have to set the warning manually, using the Hash syntax.

class FlashController < ApplicationController
  def show
    # not possible
    # redirect_to home_path, warning: "logged out"
  
    redirect_to root_path, flash: { warning: 'logged out' }
  end
end

Can we do better?

Bob the Builder: yes, we can!

A Better Solution

You can create custom flash types using the add_flash_types method. Any new types you create will behave like the existing notice and alert types.

First, add the custom flash type in ApplicationController, the base class of all your Rails controllers. This will create the custom flash helper methods that are available in all views.

class ApplicationController < ActionController::Base
  add_flash_types :warning
end

Now you can use the warning type in the redirects and access the warning helper in the views.

class FlashController < ApplicationController
  def show
    redirect_to root_path, warning: "Incomplete profiles"
  end
end

# in the view
<%= warning %>

Pretty cool.

Note: Remember that you use the add_flash_types in the ApplicationController, which creates helpers that you can use in views for all  the controllers that inherit from ApplicationController.

If you add it to FlashController and redirect to UsersController#show, Rails won't recognize the warning helper in the users/show.html.erb view.

(I know because I just spent half an hour debugging and scratching my head figuring out why it couldn't find my helper.)

Check out this article for an in-depth explanation of Rails sessions.
Sessions in Rails: Everything You Need to Know
In this post, we’ll learn about Rails sessions, including what is a session, why we need them, and why they’re so important. I’ll also take you behind the scenes and show you how Rails implements sessions and where the `session` method actually comes from. Hint: it’s not in the Rails codebase.

Internal Implementation

Let's open the Rails codebase and see how the add_flash_types method is implemented:

# actionpack/lib/action_controller/metal/flash.rb

def add_flash_types(*types)
  types.each do |type|
    next if _flash_types.include?(type)
    
    define_method(type) do
      request.flash[type]
    end
    helper_method(type) if respond_to?(:helper_method)
    
    self._flash_types += [type]
  end
end

Right away, we notice that the method accepts multiple parameters (see *types). That means you can create multiple flash types in one line as follows:

add_flash_types :warning, :info

Next, it loops over each type and skips the rest of the code block if it's already registered. For this, Rails checks the _flash_types array, which is the internal storage for the flash types.

Then it creates a new method named after the custom type using the define_method method in Ruby. All this method does is access the custom flash value from the request.flash object.

Next, the newly created method is registered as a helper method on the current controller, using the helper_method method in Rails. This is how you can access the flash type in the views, such as <%= warning %>.

Note that we also make sure that the controller has a helper_method using the respond_to? method in Ruby. This method verifies if you can call a method on an object.

Finally, the new type is added to the internal storage, i.e. the _flash_types array.

And that's how Rails registers a custom flash type.

Have you used any custom flash types it in your apps? If yes, which new flash types did you add? Let me know in the comments below.


That's a wrap. I hope you liked this article and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.