User notifications with Rails, Noticed, and Hotwire21 Mar 2022
A nearly-universal need in web applications is user notifications. An event happens in the application that the user cares about, and you inform the user of the event. A common example is in applications that have a commenting system — when a user mentions another user in a comment, the application notifies the mentioned user of that comment through email.
The channel(s) used to notify users of new important events depends on the application but the path very often includes an in-app notification widget that shows the user their latest notifications. Other common options include emails, text messages, Slack, and Discord.
Rails developers that need to add a notification system to their application often turn to Noticed. Noticed is a gem that makes it easy to add new, multi-channel notifications to Rails applications.
Today, we are going to take a look at how Noticed works by using Noticed to implement in-app user notifications. We will send those notifications to logged in users in real-time with Turbo Streams and, for extra fun, we will load user notifications inside of a Turbo Frame.
When we are finished, our application will work like this:
Before we begin, this tutorial assumes that you are comfortable building simple Ruby on Rails applications independently. No prior knowledge of Turbo or Noticed is required.
Let’s dive in!
To follow along with this tutorial, start by cloning this repository from Github and then set it up:
The starter repo contains a Rails 7 application with Turbo, Tailwind, and Devise ready to go. The starter repo uses Ruby
3.0.2, but everything in this tutorial will work fine with Ruby
3.1, if you prefer.
If you want to work from your own application instead of cloning the starter, you will need a Rails 7 application with Turbo installed and an authentication system built around a
When you’re ready to start building, start the server and build Tailwind’s css with
Our starter application comes with a Devise-powered user model and the root path set to the
Dashboard#show action which just contains links to sign in or sign out for now. Before diving in to the code, create at least one user through the form at http://localhost:3000/users/sign_up so that you can login and test notifications later in this tutorial.
Eventually, our users will be able to create
Messages for other users in the application. Each time a message is a created, a new
Notification will be created, and the user the message is for will see that notification on their dashboard.
Before any of that can happen, we need to add Noticed to our application and scaffold a Message resource. Start by adding Noticed, from your terminal:
These commands are straight from the Noticed installation docs. If your Rails app is running, be sure to restart it after adding the Noticed gem to your Gemfile with
app/models/user.rb to associate notifications with users:
Here, we added the
has_many association, as directed by the Noticed setup script.
Now our users can receive notifications, but we don’t have anything useful to notify them about. We will fix that by scaffolding a
Message resource. From your terminal:
Thanks to the magic of Rails, the scaffold generator gives us almost everything we need to start creating messages and associating them with users. Because we are using Tailwind via the tailwindcss-rails gem, the scaffold generator also includes some nice looking base styles too.
After the generator runs, update the
User model again at
Here, we added
has_many :messages to set up the other side of the messages relationship.
For convenience, we can also add a link to the messages index page in
And then make a small adjustment to the messages form so we don’t have to memorize user ids:
With these changes in place, head to http://localhost:3000/messages and create a couple of messages to ensure that creating messages works as expected.
Now that we have Noticed installed and messages ready to go, next up we will send users notifications when a new message is created.
Our goal in this section is to create and display notifications to logged in users on the
Step one is to add a new notification, using the the generator built-in to Noticed. From your terminal:
This generator creates a new
MessageNotification class at
app/notifications/message_notification.rb. Head there next and make a few small updates:
deliver_by :database stores the newly created notification in the database, which will be important for Turbo Stream broadcasts later. If we wanted to send the notification by email too, we could add
deliver_by :email, mailer: SomeMailer, as described in the Noticed docs.
param :message serializes the
message object and stores it with the notification record in the database. We use that serialized
message in the
url methods. Serializing objects into params in this manner makes it easy to access records related to the notification without managing complex references — just dump the record(s) you need into
params and profit.
We can now use our
MessageNotification to send notifications to users when a new message is created. Next we need to put the
MessageNotification class into use. Head to
app/models/message.rb and update it:
Each time a message is created (
notify_user runs and creates a new
MessageNotification, serializing the
message object and delivering the message to the message’s user. We also add has_noticed_notifications to ensure that when a message is destroyed, any related notifications are destroyed too.
With those small changes, we now have a database-backed notification system up and running. Neat!
We have notifications in the database now but there is no way for users to see those notifications anywhere, which is not very useful. Next up, we will create a
Notifications controller to display notifications to users.
From your terminal, generate the controller and a partial to render each notification:
config/routes.rb and add a notifications path helper:
Here, we scope
notifications to the
current_user, so users only see their own notifications when logged in to the application.
Update the new Notifications index view at
turbo_frame_tag wrapping the list of notifications. Our plan is to render the list of notifications on the dashboard show page — we will use Turbo Frame’s eager-loading functionality to load the content of the notifications index page on the dashboard show page.
render @notifications relies on Rails’ collection rendering to render each notification. Before this will work, we need to fill in
Here, we use
to_notification from Noticed to access the
message method we added in
MessageNotification earlier, and use the built-in
read? method from Noticed to check if the user has read the notification or not.
One last step here before users can see notifications on the dashboard. Head to
app/views/dashboard/show.html.erb and update it to add an eager-loaded Turbo Frame for logged in users:
turbo_frame_tag has an id of
notifications, which matches the Turbo Frame rendered by
Notifications#index. When Turbo eager-loads content for a Turbo Frame, it expects the url passed to
src to return a response that includes a Turbo Frame with a matching id.
The sequence of events for our eager-loaded notifications index page is this:
- A logged in user visits
- Turbo sees the
srcattribute and initiates a new request to
Notifications#indexreturns HTML that includes a
turbo_frame_tagthat matches the tag that initiated the request
- Turbo extracts the contents of the
turbo_frame_tagand uses that content to replace the content of the existing Turbo Frame
At this point, logged in users can see their notifications on the dashboard. Test it out by logging in as a user and then creating a new messages for that user. Refresh the dashboard as that user and see that your notifications are listed on the dashboard:
While our users can see notifications, they don’t see those notifications in real-time — they have to manually refresh the dashboard before they see new notifications. Let’s wrap up this tutorial by making notifications real-time with Turbo Stream broadcasts.
Real-time notifications with Turbo Streams
Turbo model broadcasts, powered by turbo-rails, make it easy to send real-time updates to users with ActionCable. When we finish this section, each time a new notification is created, a Turbo Stream broadcast will be sent over an ActionCable channel that will automatically insert new notifications for a user into the user’s dashboard notifications list.
To start, update
app/models/notification.rb to trigger a Turbo Stream broadcast when a new notification is created:
appends a new notification to the list of notifications. To ensure that only the user the notification is for receives the broadcast, we set the broadcast channel to
recipient, :notifications, as described in the
The model update handles sending Turbo Stream broadcasts, but before the broadcast will be picked up by the front end we need to subscribe users to a Turbo Stream channel. We also must ensure the markup includes a
notifications-list id (matching the
target passed to
broadcast_append_later_to) for the Turbo Stream to update.
app/views/notifications/index.html.erb, add the
notifications-list id to the
ul containing the list of notifications:
And then in
turbo_stream_from helper from turbo-rails subscribes the user to a channel that matches the channel our model is broadcasting from.
current_user, :notifications in the view ==
recipient, :notifications in the model).
When a user visits the dashboard, the
turbo_stream_from helper opens an ActionCable subscription to the matching channel. Broadcasts to that channel are picked up by Turbo and used to update the page when new broadcasts are received. The scoping of the channel to the
current_user ensures that users do not receive broadcasts intended for another user so that our new message notifications are only sent to the user the message is for.
With these changes in place, login as a user in one browser and head to the dashboard. Confirm the ActionCable channel subscription is created by checking the server logs for a line that looks like this:
In another browser, head to the new messages form and create a message, setting the user on the form to the user that you are logged in as in the first browser. If all has gone well, the new notification will be added to the list instantly, with no page updates required.
Great work following along with this tutorial, that is all of the code for the day!
Wrapping up & further reading
Today we built a simple notification system with Rails and Noticed, and we used Turbo to display new notifications for users in real-time. Noticed is an extremely powerful gem that makes the work of building and expanding a multi-channel notification system in a Rails app much simpler, and the easy integration with Turbo Streams makes it a great match for any modern Rails application.
In our tutorial application, we displayed notifications as a static list of every notification the user has ever received, with no way to interact with them or remove them from the list. We also required users to be on their dashboard to see the new notification.
In a production application, we might expand our Notifications controller to include an
update method that allows users to mark notifications as read and clear those notifications from the list.
We would would also likely build a notification indicator in the main navigation that is rendered on every page of our application with an icon indicating unread notifications (think the ubiquitous bell icon seen across thousands of web applications).
The neat thing about this tutorial’s approach is that the basic approach remains the same even for a more sophisticated implementation. Use Turbo Streams to broadcast new notifications to users and update the UI in real-time. Use built-in Noticed methods to act on notifications (like
mark_as_read! to read a notification). Use a Turbo Frame to fetch notifications for a user and load those notifications into a section of a larger page.
For further learning on Noticed, Turbo Streams, and Turbo Frames:
- Check out the GoRails tutorial on Noticed (Chris from GoRails wrote Noticed, thanks Chris!)
- Read the simple but effective Noticed docs, starting in the repo’s readme and digging in to specific delivery methods as needed
- Solidify Turbo concepts with my Turbo 101 article, and my Turbo Frames and Turbo Streams on Rails articles, for more on how to use Turbo with Rails
Finally, if you want to dig deeper into implementing notifications in a Rails application, a chapter of my book is dedicated to building a very light notification system from scratch, with inspiration from Noticed. In the book, we add real-time updates in a more realistic way, with the standard bell icon, pop-out notification list, and the ability to mark notifications as read with a click. The book is written in this same, step-by-step tutorial style, and covers building a modern Rails application from scratch with StimulusReflex, CableReady, Hotwire, and friends.
Building your own notification system is a great exercise if you want to use Noticed in a real production application later, since you will have a much greater understanding of, and appreciation for, what Noticed offers you.
That’s all for today. As always, thanks for reading!