DEV Community

Matthew McGarvey
Matthew McGarvey

Posted on

Flash Messages with Lucky

There's often a need to show users some dynamic message based on some process of the app or action on their part. The most common scenario is usually around submitting forms. The user is trying to sign in to the app but use an incorrect password so the app sends them an error flash telling them to please try again. They subsequently sign in successfullyi so the app sends them a happy success flash welcoming them back 🌈🦄. But how might you do this in your Lucky app? Let's check it out.

Adding a flash message

Going back to the form scenario, to add a flash message based on the outcome of a save operation it might look like:

class Users::Create < BrowserAction
  post "/user" do
    SaveUser.create(params) do |op, user|
      if op.saved?
        flash.success = "User created!"
        redirect to: Users::Show.with(user.id)
      else
        flash.failure = "Something went wrong..."
        html Users::NewPage, operation: op
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

This action is to create a new User and renders a success flash message if it worked and a failure message if it failed to create the user. Besides flash.success and flash.failure, there is also flash.info for general information to pass to the user. If you need a custom flash key and message you can call flash.set(:some_key, "some value").

Once a flash is set, it is accessible for the rest of the request or in the next one. The flash will be removed after that. It's so that, as in this example action, flashes can survive redirects. If you need the flash to survive longer than that, I will explain how to do it later in the post.

Accessing flash messages

While flashes are a way to pass information across stateless requests, the most common use for them is for rendering HTML to notify users of events. Here's an example of something you might have in a component or a page to display flash messages if their are any:

flash.each do |type, message|
  div class: "alert alert-#{type}", role: "alert" do
    para message
  end
end
Enter fullscreen mode Exit fullscreen mode

This iterates over every plash and displays the message to the user. When they refresh the page it will be gone. This is a nice and simple way to send the user short-lived informational messages. Much like we set them above, flashes can also be accessed individually. You can call flash.get?(:any_key) to get a message if there is one. There is also helpers for the predefined message types so flash.info? will work as well. This is useful if you display different flash message types in different ways.

Other Usefulness

Persisting flash messages for multiple requests

Flash messages are only kept for the request they were set in and the next one, but sometimes you need to persist them for longer. To do this, you can call flash.keep to persist any flash messages given to the current request onto future ones. The most common use-case for this is when redirecting requests, but Lucky already handles this scenario for you.

It's important to remember to only call flash.keep when you know you need to pass flash messages onto future requests. Calling it outside this scenario can lead to flash messages appearing to the user multiple times.

Clearing flash messages

I think the heading says enough. Just call flash.clear to immediately remove all existing flash messages.

One Warning

It's important to remember that flash messages are accessible in the request they are set in AND the next one. If you render all flash messages in HTML if there are any, setting flash messages on requests that don't redirect can also lead to them appearing to the user multiple times. For example, if you set a flash message on the user's profile page, they will see the flash message and then it will render again if they go to the home page.

Unfortunately, there's not a solution for this problem right now but keep an eye on this issue to know when we have a working solution.

Flash messages are always displayed for 2 requests #1271

I'm having trouble with the flash messages.

I'm doing something similar to the generated browser sign_in endpoint:

get "/some_endpoint" do
  flash.info = "here is a message"
  html SomePage
end
Enter fullscreen mode Exit fullscreen mode

It doesn't matter if I use flash.keep or not. The flash message shows up in the response (as it should), but it doesn't go away if I refresh or trigger another request.

I would expect it to disappear in the response to the second request.

Lucky version: 0.24.0

I noticed that in Shared::FlashMessages we loop over flash keys using #each, but in the implementation of Lucky::FlashStore #each is delegated to #all, which means we are looping over both the @next messages and the @now messages.

I also noticed that in Lucky::FlashStore#set we modify @next instead of @now, meaning there is no difference between calling flash.keep or not, as @next gets put in a cookie (i assume thats why #to_json is there), sent back by the client and put back in @now for the next response, essentially always being "kept" and thus being displayed in both responses.

I appended a monkey-patch in src/app.cr which looks like this:

class Lucky::FlashStore
  def set(key : Key, value : String) : String
    @now[key.to_s] = value
  end
end
Enter fullscreen mode Exit fullscreen mode

And it seems to work fine now, but this is not the correct implementation, is it? I'm finding the implications of the implementation a bit hard to hold in my head.

Top comments (0)