Part 2 is available here.
Source code is available here.
Let me help you!
Hey, I am Anatoly, founder of this blog. I would love to help you! I have 6+ years experience in Web Development and IT Leadership. Lets work together on your project! Leave me your email and I will schedule a FREE Consultation!
Thank you for continuing to follow this tutorial. If you stumble on this post and don’t know what happening, please check Part 1 and Part 2 that covers basic setup and styling. Also please check Live Demo for the final result. If you like to jump straight into the code, here is the source code for all 3 parts.
In this part we will hook live streaming to our lively notification center!
Say hello to ActionCable!
ActionCable allows you to open a streaming channel and stream your events live, without need to refresh!
Lets check it out!
ActionCable Magic
To create ActionCable stream first we will need to create a new channel. Lets scaffold it. Go to your terminal, navigate to notificator folder and run the following command:
rails g channel notifications
This will create JavaScript and Ruby Channel for our notificator.
Lets enable streaming from notification_channel:
notificator/app/channels/notifications_channel.rb
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. class NotificationsChannel < ApplicationCable::Channel def subscribed stream_from "notification_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end end
As you might see I have uncommented stream_form and gave it a name “notification_channel”. We will use this identifier to know where to broadcast our changes.
Also since we are here, we need to specify which route ActionCable can mount to.
Lets go to routes and add ActionCable mount route:
notificator/config/routes.rb
Rails.application.routes.draw do root to: 'notifications#index' resources :messages # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html # Websockets resemble this URL mount ActionCable.server => '/cable' end
New piece is mount ActionCable.server. This specifies that ActionCable will mount to /cable.
We are almost done with the setup!
Restart your rails server, refresh your browser. You might see following error in your rails logs:
Request origin not allowed: http://0.0.0.0:3000
I have create separate post how to resolve it ,but for your convenience, I will repeat it here.
Go to notificator/config/environments/development.rb and add following line:
config.action_cable.allowed_request_origins = ['http://0.0.0.0:3000']
Note: You can also put http://localhost:3000 separated by coma if you want
Restart your server, refresh the page, and now you should see something like this:
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket) NotificationsChannel is transmitting the subscription confirmation
This means that channel is working properly! Good job on that!
Our next step is to make our counter to update live. We also want to have a bit of animation when counter number changel. For this we will use JQuery transition. Please download library from here . And put it into:
notificator/vendor/assets/javascripts/jquery.transit.min.js
To make this file available everywhere, lets also include it in application.js:
notificator/app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. // // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery //= require jquery_ujs //= require turbolinks //= require jquery.transit.min //= require_tree .
As you see, I have added this library right before require_tree. Lets refresh page and make sure nothing blew up 🙂
Since we have transitions, lets use them!
Lets go to our channel/notifications.coffee, and add custom behaviour for when our channel receives new data.
notificator/app/assets/javascripts/channels/notifications.coffee
App.notifications = App.cable.subscriptions.create "NotificationsChannel", connected: -> # Called when the subscription is ready for use on the server disconnected: -> # Called when the subscription has been terminated by the server received: (data) -> # Called when there's incoming data on the websocket for this channel this.update_counter(data.counter) update_counter: (counter) -> $counter = $('#notification-counter') val = parseInt $counter.text() val++ $counter .css({opacity: 0}) .text(val) .css({top: '-10px'}) .transition({top: '-2px', opacity: 1})
We have added update_counter function, that is triggered whenever stream receives data. update_counter functions changes text of the counter to a new number, and does it using transition from the top.
To make it work we need to pass new counter value to the stream.
Since event streaming is a very expensive task, we don’t want to do it in foreground. Lets create background job that will take care of it. Go to your notificator app in terminal and run:
rails generate job NotificationBroadcast
This will create app/jobs/notification_broadcast_job.rb . Lets go there aand via this job broadcast our new counter value when new notification is created.
notificator/app/jobs/notification_broadcast_job.rb
class NotificationBroadcastJob < ApplicationJob queue_as :default def perform(counter) ActionCable.server.broadcast 'notification_channel', counter: render_counter(counter) end private def render_counter(counter) ApplicationController.renderer.render(partial: 'notifications/counter', locals: { counter: counter }) end end
We did here 2 things:
First we passed counter to perform job action, which broadcasts this new value to ActionCable notification_channel that we have created earlier.
Second we made it broadcast whole partial with updated counter value.
Perfect!
Last piece left before we can see our counter updating live. I promise!
To make it work we need to call this job and pass it counter value. We also want to do it every time new notification is created.
Lets put job creation into after_create_commit hook of Notification.
notificator/app/models/notification.rb
class Notification < ApplicationRecord after_create_commit { NotificationBroadcastJob.perform_later(Notification.count)} end
This after_create_commit hook creates delayed job and passes notifications count to it. Very straightforward.
Enough talking, lets see it. Restart your server. Open 2 browser windows side by side.
First window: http://localhost:3000/messages/new
Second window: http://localhost:3000/
Now create message in the first window, and look at counter in the second window.
Boom! Magic 🙂
Pretty amazing, hugh ?
The only piece that I want to add here, is to append new comment to the modal as well.
This will allow us to look at the whole flow once again.
Lets make our notifications more descriptive. Lets make them show actual Message.
notificator/app/models/message.rb
class Message < ApplicationRecord after_create_commit { notify } private def notify Notification.create(event: "New Notification (#{self.body})") end end
In line 7 this time we are passing “self.body”, this way we will able to see message body in our modal.
Next lets update our broadcasting job to accept and render notification partial:
notificator/app/jobs/notification_broadcast_job.rb
class NotificationBroadcastJob < ApplicationJob queue_as :default def perform(counter,notification) ActionCable.server.broadcast 'notification_channel', counter: render_counter(counter), notification: render_notification(notification) end private def render_counter(counter) ApplicationController.renderer.render(partial: 'notifications/counter', locals: { counter: counter }) end def render_notification(notification) ApplicationController.renderer.render(partial: 'notifications/notification', locals: { notification: notification }) end end
We are passing notification to perform, from there we are passing it to ActionCable but first we render it as partial. (Same as counter before)
For this to work we need our Notification to be passed to our job. Lets do it by updating hook in Notification and passing self:
notificator/app/models/notification.rb
class Notification < ApplicationRecord after_create_commit { NotificationBroadcastJob.perform_later(Notification.count,self)} end
Cool! Last piece, we need to append our notification to notifications list via JavaScript. Remember how we updated counter ? Lets do the same with notification:
notificator/app/assets/javascripts/channels/notifications.coffee
App.notifications = App.cable.subscriptions.create "NotificationsChannel", connected: -> # Called when the subscription is ready for use on the server disconnected: -> # Called when the subscription has been terminated by the server received: (data) -> # Called when there's incoming data on the websocket for this channel $('#notificationList').prepend "#{data.notification}" this.update_counter(data.counter) update_counter: (counter) -> $counter = $('#notification-counter') val = parseInt $counter.text() val++ $counter .css({opacity: 0}) .text(val) .css({top: '-10px'}) .transition({top: '-2px', opacity: 1})
One line change: in received function we are prepending data.notification .
Thats it. Now lets try two window magic one more time. Restart your server, refresh your browsers.Open and keep your modal opened in one browser window, create new message in other browser window:
What you should see is how notifications are appearing live as well as counter value changing.
Congrats! You have done it!
Thank for you completing the tutorial.
This is it for coding part. If you are curious how to deploy it to Heroku, please read Part 4.
Please post your questions into comment section.
If you like what you see please Subscribe to my blog. If you need help with your project, please Get In Touch.
Thanks for installing the Bottom of every post plugin by Corey Salzano. Contact me if you need custom WordPress plugins or website design.
Hi Anatoly,
Thanks for this tutorial, really easy to understand :), but i have done everything you instructed me to do, unfortunately, above code does not work for me, what is wrong with my code? FYI, i am using rails 5.0.0 in development environment, thank you for the help
hey, thanks for the awesome tutorial, one question , if I would like to the counter to disappear when the notification bell is clicked how do I achieve that ?