Based on a talk I gave at the NYCHTML5 Meetup on June 2nd, 2015.
Any web app that involves real-time interactions between users requires some form of live notifications. Implementing this, for a beginner developer such as myself, can be a daunting challenge. This post will walk through how I went about solving this problem when working on Lacquer Love&Lend, a social network for nail polish lovers that allows users to interact via friendships and lacquer loans. As with any social network, I wanted users to see live notifications whenever they received a new friendship or transaction request, or when the state
of any of their friendships or transactions changed. The example that follows assumes some basic knowledge of Rails.
Some Basic Ajax
In a basic Ajax request, a user clicks on something, the Ajax request gets sent, and a part of the DOM gets updated without the entire page reloading.
The code usually looks something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
For the basic example, I’m omitting the controller and view as the main focus of this post is how I implemented live notifications. A more detailed explanation of basic Ajax follows in my next post - a prequel to this one, if you will :).
With the code above, a single user’s action of submitting the form sets off the whole chain of events. But for live notifications, more than one user is involved and the action that changes one user’s data is hapenning on another user’s client! Making this happen twisted my brain into a pretzel at first, but after several attempts I got the functionality I wanted. A description of these follows below.
Attempt #1: Refresh a Single Div Every 3 Seconds
In order to get a single part of the page to update without the entire page refreshing, I used a setInterval()
function to make an Ajax request every 3 seconds. This would make a GET
request to a custom route: users/:id/live_notifications
that hit an action named live_notifications
in the UsersController
.
1) Separate the “live notifications” div into a partial:
2) Create a route and a controller action:
1
|
|
1 2 3 4 5 6 7 |
|
3) Make the Ajax request to hit users#live_notifications
every 3 seconds:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
4) Once this Ajax request hits the controller (which is set up to handle a JavaScript response in the respond_to
block), Rails by default will look for app/views/users/live_notifications.js.erb
and execute the following to refresh the partial:
1
|
|
This is all it took to refresh that single div every 3 seconds. However, it was far from ideal:
Lots of refreshing for no reason (like when you’re looking at another user’s page and no notifications are displayed, or when nothing has changed)
Things that never would change are part of the div that is being refreshed (like header text, for example)
Last but not least, this kind of indescriminate refreshing breaks the functionality of forms…
A Quick Fix for Form Problems
Thanks to jQuery pseudo selectors, we can stop the Ajax call from being made if an input field is currently focused:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Getting More Specific
The next logical step up from the “refresh everything all the time” strategy was to refresh the live notifications div only when looking at one’s own show page (in other words, when the user id in the url matches the id of the current user stored in the session).
In order to make the current_user
from the Rails backend available to JavaScript, I put the following in my application layout:
1 2 3 4 5 |
|
With current_user.id
stored in an object attached to the window, making sure the Ajax call only gets made when a user is looking at his/her own profile page is simple:
1 2 3 4 5 6 7 8 9 10 |
|
The Final Refactor: Only Refresh When things Have Changed
To change only things that have changed, when they have changed, the ability to compare what’s on the back end with what’s on the front end is needed.
For this part, I needed to capture the state of all of a user’s transactions and friendships (the two things for which there may be a notification), and hide this information on the page. I created a method in the User
model that returns all of these states in an array, and put this into a hidden element on the page:
Since the transactions could also have due dates, I did something similar for those.
The next step was to change the code in app/views/users/live_notifications.js.erb
to check the current state of the user’s transactions and friendships and see if the #all-categories-tracker
is up to date:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
With this final refactoring, checks are in place that stop the unnecessary refreshing that came with the first version:
Alternatives
The method I described above for achieving live notifications is very basic Ajax polling. Every serveral seconds, a request/response cycle fires. This inevitably means lots of database querying, even if the amount of refreshing can be reduced to a minimum. In my search for ways to reduce the burden this puts on the database, here are some other techniques I’ve found that may offer some advantages:
1) Long Polling
With this technique, a request fires and waits for a change before sending a response. Then another request can be fired.
2) Web Sockets
Very different than Ajax polling or long polling, web sockets are used for continuous communication between server and client.
3) Server-Sent Events
Unlike web sockets which allows for continuous back and forth from server to client, this technique establishes a persistent connection that allows the server to send data to the client, but not the other way around.