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
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
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
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
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)