Skip to main content
Realtime Multi-user Spreadsheets:

Multi-User Spreadsheets with ActionCable: Part 2

Episode 138 · September 13, 2016

Build a reactive multi-user spreadsheet web app with ActionCable and RethinkDB



Earn a free month

This will allow us to grab that stream from the NoBrainer streams gem that we were just looking at in GitHub, so this is going to allow us to use that to stream stuff across ActionCable, so we'll be able to do that, and we'll paste that into app/channels/active_users_channel.rb. So it appears that the ActionCable stuff here, is basically when you subscribe, when you join, when you open a tab up and you've basically joined the ActionCable connection, you will subscribe. That will say: Let's just create a new user, it doesn't take any data, so this could be changed to use like current user, if you're doing something like Google Spreadsheets where you have accounts and your users are logging in. This is more of the anonymous user, so we're going that approach, and then, this is creating it in the database and then we're using the data to stream from, so this is saying: stream from the user's table for the most part, I'm not sure what include_initial does, but I'm sure we can look it up and NoBrainer::Streams if we want to find out. I'm going to say that's probably not super important for us at the moment, so let's keep going with the tutorial, but this should set up our stream to RethinkDB automatically, so that's pretty cool. Let's refresh the page here, after I restart my rails server, and refreshing the page should connect to ActionCable, which would start the subscription on the channel, which should create a record in RethinkDB, so if we go to localhost:8080 where the admin console is, we now have one table, and we have 4 MB of disk space used, which is interesting, and so we have a user's table now. I don't know if we can see the records in here, this is probably about five documents, so I don't think that you can actually cd records directly. Maybe there's a way to do that, I'm not entirely sure.

There's about five documents, something like that, I think there's actually one, but this is more for an overview of your database as opposed to like a direct access to view it, so this is possibly saying: About 5 because it's less then 5, and if it's that, then we don't care. Maybe that's how that works, I'm not entirely sure. That did create user table and I'm sure included the record. We can probably also look at our logs and see, here we go at the yellow and the red, we can see that it created the spreadsheets_development in the database, and then the table_create created a user with a shard and a replica and a primary key, and it inserted an id, and yeah. It seems to have worked, and it seems to have created ActionRecord is loaded which we probably don't want, we can remove ActiveRecord.

This automatically created the database, it created the table, it created the record, which is all steps that you would have to do yourself with ActiveRecord because you have to create your database, you have to run your migrations, create the table, you have to do all that all yourself before you can insert a record, so this does it automatically. It's kind of one of the niceties of NoSQL or that style of database, because it's a lot more free flowing, which is nice. But there's not actually structure, so you can't count on every record being the same, because you could add in some extra fields in there, and if you don't write your code flexibly to be able to detect and use those, then your code is not going to get really any benefits from using NoSQL, because you really just wrote structured data in NoSQL, and it would probably be better in relational database.

Anyways, let's continue on with application and check this out. subscribed lifecycle callback creates a new user, unsubscribed deletes that user, so this will maintain that list of users in the database and make sure that we're not creating a bunch of users, and then they're disappearing. I'm curious, when you close the tab here, this should actually create that, as you saw, there's the red mark there, it jumped, which means that it did a write, and now we have zero documents, which means that we had one document before, we deleted it, and now we have zero documents, and we're able to watch that in real time. This admin is actually doing the watching for chainsets or whatever in order to get real time updates as well as the management console, that is neat as well.

Everything seems to be working fine, the unsubscribe happens automatically when you close the tab, the connection knows that it was disconnected, the unsubscribe runs, deletes the user, and our database is kept clean, and we don't have hanging users in that table. That's pretty cool, let's go over into our


<section id="active_users">
    <h2>Active Users</h2>
    <ul id="active_users_list">

Let's just paste this in here, and we can open up localhost:3000, we can see our active users listed there, but we need to write some code in order to basically add those. Let's put these in the app/assets/javascripts/channels/ We can replace that, because we don't really need the connected or disconnected ones, we only want the data that the stream from on the server side channel does, so this stream from is basically going to say that any of those changes in the users table will automatically get piped over, and so this is going to listen for receiving data, and it will have old value, new value, and I believe that these are coming from the RethinkDB chainset itself as the structure, and so we can console.log data in order to see what's coming across, so if we print that out, we should see this object. We have new_val, and so we can see that there's that new user, and I bet if we open up localhost:3000 again, we'll see another one, and we do, so we see new_val and old_val, but old_val was null here. The first one didn't include old_val, but these must be coming then from NoBrainer as part of that chainset or chain feed on the table. That's pretty cool.

Let's see if we still have that JavaScript error there. this is in the active users folder. It's trying to call those methods, which we haven't defined yet, so this is going to be where we define those. We're going to create App.spreadsheet, it's going to keep track of the active users. When we add a new user in, we give it the user from RethinkDB, we've got the id on it and add it to the active users hash there, and then we render the active users. When we remove the user, of course, we delete them from that list and then we re render and the render active users, and simply says: Let's grab that div and set the inner html, we'll grab that id on whatever tag it's a ul, and then we can just go through each of those users in our list and insert list items for every one of those. Grabbing this, we can create a new file app/assets/javascripts/ This is going to be what the channel there does, App.spreadsheet is this file, so the channel is being receiving data, and basically it determines what it needs to do with that data, and if it's a new user, then it will go talk to the spreadsheet App.spreadsheet and say: Add in this new user, and remove that user if it's time to remove one. This is just kind of delegating what to do with that data that's coming in, and the App.spreadsheet is actually determining: We'll add this user, we'll remove this user and we'll re-render on the page. Let's see what we've got now in our application.

As soon as you render the page, this is empty, and then the JavaScript runs and it receives that stream, and then it gets the initial list, so I'm pretty sure that that's what's happening when you say include_initial: true, you get the initial list of users in the database. For example, if we remove this, you don't get anything, and the reason for that is because you just added yourself to the database before you started streaming, and that does not include you, because this will only get changes since then, so you have to include the initial records in the database as you get started, so that you have a list of all the current users that were there before you, and if you don't do this, you will be a little bit out of sync because you won't see those people. That allows you to stay up to date with those pre existing users.

We've made some good progress so far, and learned some cool stuff about RethinkDB, and now it's time to take all that and actually go and build our spreadsheet with it. So it looks like what we're going to be doing is we're going to be using jQuery and HandsOnTable which looks to be a pretty cool JavaScript spreadsheet for the web. It appears to be used at a bunch of awesome places, which is cool, and it also appears to be a paid product, so I guess we're going to be using their open source version or whatever. Their free version, so it looks like they have some licensing for it, it's going to be a very well supported project because they are making money from it, so you can trust that it's going to be really good, and going to have a good API, be well maintained, which I like. I like to be able to support this kind of stuff, it's the same thing from you guys, supporting me, making these videos, so I appreciate that. You guys are awesome.

HandsOnTable looks cool, we're going to be using that. It looks like you're going to grab the CDN from cloudflare, so we're just going to toss these into spreadsheet/index.html.erb file and put that up here at the top. This is just going to include it from the CDN. We could actually download the CSS and JavaScript, and put it in the gem, or we could do whatever we wanted with it. But what we're going to do is pull it from cloud player CDN, which will just allow us to inject this in without downloading any files, setting up any other dependencies, we'll just have this available, which will be very nice for us because this will be easy to set up for the demo. Chances are, if you actually are going to do this and you have that spreadsheet on many pages, you'll either want to include this in your application.js file or your application layout so that it's available kind of more globally. But we are only using it on this page, plus it's a demo, so it's only going on this page because it's really the only page of our application, and they do mention that if you have more front end skills or whatever, you could use React or Polymer, or Vuejs, or any of the front end frameworks to either build your own table, spreadsheet type of thing on the front end, or you could refactor the jQuery stuff that we're doing here, kind of clean all this up. Since this is just a demo, we're going to go update that spreadsheet coffescript file and add a set up function, and then we're going to have it automatically start that


This is going to say: Find that element on the page that's called spreadsheet, we're basically just going to call the hands-on table javascript on that spreadsheet element, and then we'll set up some configs for it, and then we'll save a reference to the hands-on table javascript instance of hands-on table, so we'll be able to access that in the future if we do want to do that. This will just load up when jQuery dom is ready, then we'll go ahead and set up the spreadsheet. Saving this should allow us to see that spreadsheet, and it did not, so let's take a look at the console. HandsOnTable is not defined, I believe we did everything appropriately here, but maybe this is running faster than the include is happening. In the application.html.erb you're probably going to need a <%= yield :head %> so that the content for can actually take those two lines for the CSS and JavaScript and put them in the head. I didn't notice that before, but spreadsheet index is actually saying content_for(:head) so this page probably does not have those links to include that JavaScript, and it doesn't appear to, so what we're going to see now is if we refresh because we put that yield head in the head, you get the script and the link up here, so once the page loads, you'll install the HandsOnTable library and the CSS so that your application JavaScript once that is ready will have access to HandsOnTable, and we don't get the same error as we did before, but we do still get an error.

insertBefore of null, we can go to our application.js and we've got all this, we can also try application.html.erb and put the <%= yield :head %> above this, so we can make sure hands on table loaded before our application JavaScript and that's still the same error, but at least the active users is not failing now. HandsOnTable is still having this insert before issue. Of course the thing that I overlooked was creating this section with the id of spreadsheet, and that means like our JavaScript was running correctly, but it had nowhere to insert it. It's not the most intuitive error, but if we actually include the spreadsheet tag, refreshing the page should get no errors and we get a spreadsheet. This is cool, we get to click on these cells and it will select them and that comes out of the box with this spreadsheet hands on table, and same with the CSS, so this is working and you can keep your <%= yield :head %> at the bottom, it doesn't matter. Either way that you go, the JavaScript will load appropriately because you're waiting for the jQuery dom ready event to fire before you instantiate anything. Either place you want to put the yield head you can do that, and all of this is now working, so we're caught up and I just missed that we want to put the <% content_for(:head) %>, <%= yield :head %> and the section in the appropriate spots, so that that will all be working, and now the next step will be to strain field stream field selection .

Transcript written by Miguel


Subscribe to the newsletter

Join 31,353+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.