DISCLAIMER Some time ago, I wrote an article here on how to build a notifications system with Rails and Redis from scratch. This new one is a quick update on that one. If you don't know what I'm talking about and are curious about how to accomplish the same thing without Hotwire, here you go: 👉 Real-Time Notification System with Sidekiq, Redis and Devise in Rails 6 Rails 7 was a huuuge update, but I'm not going to talk about that in this post. -officially- became a part of Rails 7. Now, you can have real-time interactions and SPA looking-Rails projects, without even writing a single line of JS. Hotwire TL;TR Hotwire can be understood as an umbrella/approach. Inside this thing, you will find 2 little boys, Turbo, that replaces TurboLinks, and Stimulus, which is, in their : or the thing that we're going to use when we need to listen to some button clicks :p own words A modest JavaScript framework for the HTML you already have, Hands-on 👊 I'm going to use a clean project, you can if you want. Therefore: skip this $ rails new notifications_with_hotwire -d=postgresql -T ??? specify the DB that we're going to use. By default, Rails uses sqlite3. -d=postgresql skips test files. -T To make it more real, let's include and with their icons library, : Devise Tailwind Heroicons $ bundle add devise tailwindcss-rails heroicon Now we need to set up those gems... For tailwind and heroicons, just run $ rails tailwindcss:install && rails g heroicon:install For Devise, run and follow their notes. $ rails g devise:install && rails g devise User I'm also going to create a resource that will dispatch notifications later on: . Don't forget to assign user on create, $ rails g scaffold Post title body:rich_text user:references before_action :authenticate_user!, except: [ :index, :show ] def create @post = current_user.posts.new(post_params) .... And of course, specify the relationship on the User model with: has_many :posts Finally, let's move on to what you're looking for 😉 How to create notifications I want to create notifications for different resources, such as posts, comments, or likes. For this, I'm using a polymorphic reference. If you're not familiar with it, read about it . here $ rails g model Notification item:references{polymorphic} user:references viewed:boolean Just add a default value for viewed field on the migration: t.boolean :viewed, null: false, default: false Build a basic nav and place the notifications count badge on it with something like: <%= link_to notifications_path, class: "bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" do %> <span class="sr-only">View notifications</span> <%= heroicon "bell", variant: :outline, options: { class: "h-6 w-6" } %> <%= tag.div id: :notifications_count do %> <%= render "notifications/count", count: current_user.unviewed_notifications_count %> <% end %> <% end %> You may notice that unviewed_notifications_count doesn't exist on our User model, so let's define that as a class method on : user.rb has_many :notifications def unviewed_notifications_count self.notifications.unviewed.count end And the partial is needed for Turbo. Going to explain this below. app/views/notifications/_count <%= tag.div id: :notifications_count do %> <% if count > 0 %> <span class="h-6 w-6 flex items-center justify-center bg-red-500 rounded-md text-sm"> <%= count > 9 ? "+9" : count %> </span> <% end %> <% end %> Here's the thing about the whole article ( ). Turbo actions. Let's take the first callback to explain what's going on. notification.rb : now turbo handles the stuff we built on the . TD;TR: ApplicationCable to establish the connection between the user and the server, create and send jobs to the background to be performed async with Redis. broadcast previous article : simple JS actions prepend/replace/remove : the target (AKA ). In this case, I'm establishing a connection with the specific signed user. to stream : simple HTML (or ) element used to perform the JS action. target turbo_frame scope :unviewed, ->{ where(viewed: false) } default_scope { latest } after_create_commit do broadcast_prepend_to "broadcast_to_user_#{self.user_id}", target: :notifications end after_update_commit do broadcast_replace_to "broadcast_to_user_#{self.user_id}", target: self end after_destroy_commit do broadcast_remove_to "broadcast_to_user_#{self.user_id}", target: :notifications end after_commit do broadcast_replace_to "broadcast_to_user_#{self.user_id}", target: "notifications_count", partial: "notifications/count", locals: { count: self.user.unviewed_notifications_count } end end Yup, the scope didn't exist either, I like to define that on . latest application_record.rb scope :latest, ->{ order("created_at DESC") } In , create the stream that allows the previous snippet "talk" with the signed user (without this, everyone's getting everyone's notifications 😛) application.html.erb <% if user_signed_in? %> <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> <% end %> Now let's put notifications-related stuff on . concerns/notificable.rb will append where we import this concern. It sets a relationship and has a simple callback that runs once we a new resource (post in this case) object. included create If that resource model has the user_ids method, it will create the notifications for that user. module Notificable extend ActiveSupport::Concern included do has_many :notifications, as: :item, dependent: :destroy after_create_commit :send_notifications_to_users end def send_notifications_to_users if self.respond_to? :user_ids self.user_ids&.each do |user_id| Notification.create user_id: user_id, item: self end end end end So then, when we want to create notifications for a new resource, we just need to include that concern on our resource model, in this case post.rb include Notificable def user_ids User.where.not(id: self.user_id).ids end Finally, to show the notifications on our application UI, let's respond to /notifications path on notifications_controller.rb class NotificationsController < ApplicationController before_action :authenticate_user! def index @notifications = current_user.notifications @notifications.update(viewed: true) end end Don't forget to notify the controller about this new path on config/routes.rb resources :notifications, only: [ :index ] And there you go. The notifications index :) views/notifications/index.html.erb <div class="space-y-4"> <p class="text-lg leading-6 font-medium text-gray-900 flex justify-between"> Notifications </p> <ul class="border-t border-b border-gray-200 divide-y divide-gray-200" id="notifications"> <%= render @notifications %> </ul> </div> The previous will look for the partial. render @notifications views/notifications/_notification.html.erb And I like to render a new partial here since we have a polymorphic relation. <li class="py-4" id="<%= dom_id(notification) %>"> <%= render "notifications/#{notification.item_type.downcase}", notification: notification %> </li> That previous will look for (in this case). If you create notifications for, let's say, likes, then you need to create a new partial, called . And customize the notification for that new resource. render views/notifications/_post.html.erb _like <p class="text-gray-900"> <%= notification.item.user.email %> just posted: <%= link_to notification.item.title, notification.item, class: "underline font-medium" %> </p> Here's the with everything :) repo Oh, and I'm building a job board for devs that want to work remotely. There are already a couple of job offers for Rails devs, if you're looking for a job, . check this out Bye. Also published here Lead Photo by on Johannes Plenio Unsplash