WebSockets in Rails 5

Rails 5 is on the horizon. There are many great new features in this new version, but the one I'm most excited about is built-in WebSockets support.

Intro to WebSockets

In case you're not familiar with WebSockets, let me give you a crash course. In a traditional web application, the browser sends a request for a web page and the server responds with the page.

without WebSockets

When the page is returned, the connection is closed. This is great if the page never changes, but it poses some problems when there are new updates on the server. The only way for the browser to get those changes, is to send another request and get another response.WebSockets solves this problem. In a WebSockets application, instead of making a request, the browser makes a connection to the server. This connection is persistent, and it's two-way. At any point during the connection, the server can send data to the browser, or the browser can send data to the server.

with WebSockets

Action Cable

WebSockets has been around for years, and many applications are already using it. In fact, you can even use it in Rails 4, via several different gems. What Rails 5 provides is built-in support, via Action Cable.There's some initial setup to be done and you can find those details here. I won't get into the setup here, because it's subject to change, and it's not what I want to talk about.What I do want to talk about, is how Action Cable can be used to add real-time capabilities to Rails apps.In general, there are two components involved. A back-end channel, which is just a Ruby class that inherits from ApplicationCable::Channel] and a front-end subscription, which is just a Javascript object with some specific functions defined.

Creating a dog barking app

For this example, I want to make an app that will allow dogs to broadcast their bark to anyone listening.The first thing we'll need to do is set up a channel. To do this, we create a Ruby class, and inherit from ApplicationCable::Channel. We'll name this channel BarkChannel:class BarkChannel < ApplicationCable::Channel
def subscribed
stream_from "barks"
end
end
The other thing we're doing is setting up our subscribed method. This will get called when a new connection is made. Inside of it, we're calling stream_from "barks". That tells the back-end that this connection should receive all messages on the "barks" channel.Next, we need to set up the front-end to make a connection with the server. This is done via the following CoffeeScript:App.dogs = App.cable.subscriptions.create "BarkChannel",
connected: ->

disconnected: ->
The important part of this code is App.cable.subscriptions.create "BarkChannel". That makes a connection to the server, via the Action Cable channel "DogChannel". The connected and disconnected functions can be used to do things when we begin and end communication with the server.Next, we would need some UI that allows the dog to input a bark. I'll skip that part, for simplicity. Once the UI has been triggered, we'll call a bark function on our App.dogs object:App.dogs = App.cable.subscriptions.create "BarkChannel",
connected: ->

disconnected: ->

bark: ->
var myDogsId = 1;
@perform("bark", {id: myDogsId})
The bark function is first setting up an id for our dog. In a "real" app, this would probably be passed from the back-end when the connection is first made, but for simplicity, I'm simply hard-coding it here.Then, we're calling @perform, which is a "magic" Action Cable command that tells it to send the data ({id: myDogsId}) to the back-end side of the channel, using the bark method. So, let's set that method up:class BarkChannel < ApplicationCable::Channel
def subscribed
stream_from "barks"
end

def bark(data)
my_dog = Dog.find_by(id: data[:id])
my_dog.bark
end
end
Here, we're finding the Dog with the given id. Then, we're calling bark on it, which will handle our bark logic. At this point, we've made a full connection from the front-end to the back-end, which can be used to do any number of things.For our app, we're going to want to broadcast that bark event out to all of the other dogs that are listening. Let's use that bark method we've called to do that:class Dog
def bark
ActionCable.server.broadcast "barks", {id: id}
end
end
ActionCable.server.broadcast is the method used to send a message to all clients listening on the "barks" channel. We're also sending a hash including the id of the dog who barked.The last thing we need to do is write the code on the front-end that will handle the broadcasted message. We do that, using the received function:App.dogs = App.cable.subscriptions.create "BarkChannel",
connected: ->

disconnected: ->

received: (data) ->
# Handle the bark

bark: ->
var myDogsId = 1
@perform("bark", {id: myDogsId})
And that's it! We've made a complete request cycle, from the front-end to the back-end, and back again. However, to be clear, the communication does not need to be initiated by the client. That fact is what makes WebSockets so powerful; the server can send messages to the client, at any time. So, a social media site could instantly notify users of a new notification. Or, a web-based email client could notify users of new emails without needing a refresh or continuous polling. How about you try your hand at creating a simple app like the sample above and see the beauty of WebSockets for yourself!Need extra muscle on your next Rails project? GET IN TOUCH.

Subscribe to the Smashing Boxes Blog today!

Smashing Boxes is a creative technology lab that partners with clients, taking an integrated approach to solving complex business problems. We fuse strategy, design, and engineering with a steady dose of entrepreneurial acumen and just the right amount of disruptive zeal. Simply put, no matter what we do, we strive to do it boldly. Let's talk today!

Related Posts

Merging Rails and Ember-CLI - Part I

To say that Ember has grown over the past year would be an understatement.

view post

Merging Rails and Ember-CLI - Part II

In part 1 of Merging Rails with ember-cli, I talked about techniques that I use to achieve a smooth development experience when combining a Rails API with an Ember CLI-powered front end.

view post