data("notifications") on that object, and we ill get an array of json objects back. This is all the code that we actually need in order to reference that json thats preloaded into the page. So when the page loads, we can go an render immediately. All we're going to have to do, is go into our notification.js.coffee, and let's comment out this code here so that when the page loads, we'll never display notifications. Here let's actually use that preloaded json that we have on the page, and let's just
console.log @notifications.data("notifications"). This is that part that we're already doing in the console, so we just need to use that variable to access the embedded notifications. When our page loads, we should see that same array gets printed out, and it does. This is cool, we've already got that going, and that just means that we now have the array, and as it turns out, that's the exact same data object as we get back from the AJAX request. That means we can just simply call
It might be kind of surprising that we can literally reuse the handleSucess here from rendering after we get the json back, but that is all we really need to do. That means you could actually go through and do this, immediately upon page load, and then you can also use that code for setTimeout just to simulate your browser being slow. You can actually replace this with a setInterval, and you can set it up to check for new notifications every 5 seconds, for example, and then you would get sort of a simulated real time set of notifications. That would be all that you would have to change for that to preload them immediately on the page, and then to check for updates every 5 seconds. It's pretty cool, it allows you to really have it easy to work with notification system, and then the last piece that we have to do is to go back here and replace this hard-coded notification with the actual notifications that we get from generating that json. Let's go ahead and do that now. Let's pull in the json that we hard-coded before, and let's replace this with rendering that exact same template that our application uses for notifications. That's the notifications/index.json.jbuilder file. This is the file that you want to render from inside this other template, and you can actually simply do that by calling
<%= render template: "notification/index", formats: [:json] %> otherwise it won't look for a partial, and here you say "notifications/index", and because this is an html request, you're actually going to need to specify formats as json in order to force it to render the json format. That's really all you're going to have to do template-wise, but remember, this json jbuilder file is rendering the @notifications variable that is set in the notifications controller. This is the unread notifications for the current user, and we're not going to be necessarily inside this controller all the time. This is the application template, so this is for every single page that is rendered, we're going to need this variable to be queried. Now it's going to be pretty quick, and so we're going to be able to do that to preload it if your servers are fast and scaled up to the point where this is going to be making sense to preload this information. So we would actually need to go into the
before_action :set_notifications, if: :user_signed_in? def set_notifications @notifications = Notifications.where(recipient: current_user).unread end
That should be all we need to do in order to set those notifications up dynamically. If we inspect that notifications again, we'll see that it's automatically parsed into a json object because the data notifications is already handled that way, which is pretty nifty, and so our notification.js.coffee really just needs to stay exactly the same and keep this set interval. It might make sense to rename the setup then, and move this out, so you would want to set this up to be run, instead of then, we want to do it there, and then we'll also
getNewNotifications() every 5 seconds instead. That should do it, and of course, getNewNotifications should be set to the instance method there, the @ method, and it probably makes sense to also do this inside the if statement. We'll check to make sure that that object exists before we do any of the work with it. These are really the two set up methods that we're going to have now, and then this will just be called every 5 seconds. That means that we should be able to go back to this page, and then go to our other user and say: Hello, and we should go back to this, and if we wait for about 5 seconds, we should now see that number change to 4, and it does.
Transcript written by Miguel
Thank you for this video, very interesting way of autoloading new notifications.
However, polling sounds a bit "old school". I read a lot about Active Live and websockets with the upcoming release of rails 5. Wouldn't it be interesting for this case to share new notifications automagically to all users concerned by them as soon as they are created ? Or maybe it's too heavy ?
Polling works just fine in a lot of cases unless there's an absolute _need_ to have realtime. It's rare for notifications to need to be truly instant. The other beauty of polling is that you can just write regular old Rails and JS code which you're already familiar with scaling. Probably 99% of the time, polling will do the trick just fine.
However, that's something we'll be talking about in the future. WebSockets are a whole lot more complicated to setup, manage, and maintain, so it will be a topic for a separate series (one that's coming soon!).
Or, shorter, using the alias "j":
<li class="nav-item btn-group" data-behavior="notifications" data-notifications="<%=j render template: "notifications/index", formats: [:json] %>">
Otherwise, a quote character in the generated JSON can break your HTML.
Why have you decided to use CoffeeScript here? To me it seems really hard to justify when features are incompatible with ECMA 6. This makes me a little nervous about relying on these lessons.
If I were you, I wouldn't be looking forward to dealing with conflicts with keywords like `for..in`, `class`, and even `super`. But to each their own.
No particular reason. I have been using CoffeeScript before ECMA 6 and am familiar with it so it's easy to use. Once nice thing is it's easily translatable to JS as well for anyone not familiar with it.
And yes, ECMA 6 (and 7) solve pretty much everything there. I'll have to do an episode showing how to use ECMA 6 with Rails as well.
Chris, is using
setInterval good for our Rails app performance?
It will add extra requests, but they are pretty simple, plus you can enable caching around the JSON response to make it fast.
Great as always. Something i noticed is when
setInterval is fired and if the drop down menu is open, it will make disappear its content. How could we prevent that ?
Thank you for the video, Chris! I implemented the notification feature by watching the previous tutorial and it works great! But it's tedious to test in the browser because I have to be logged in as two different users. I tried writing Jasmine test and capybara test but I'm a bit stuck. It'd be awesome if you could cover testing for this feature, too. Thanks for the tutorials!!
You can use this approach still the exact same way with ActionCable if you want to preload some data in the view before the Websocket connection gets initialized.
For the most part, with websocket stuff, I imagine in most cases you will want to just display a loading spinner instead while the connection gets setup. They'll happily work together though with no problem. The only difference is that you change the mechanism to update the data from AJAX to Websockets.
If I'm using active model serializer, the way to do this on the view is to use `@notifications.to_json` on the render?