WebSocket for a Ruby-on-Rails App

by Dmitry SavitskiFebruary 28, 2013
Supported by code samples, this blog post explores how to quickly start with the websocket-rails gem in a Ruby-on-Rails app.

I’d like to look at a relatively new project on GitHub called websocket-rails built on top of Faye (it’s a Ruby version), EventMachine, and Redis for data storage. In short, the websocket-rails gem is a Ruby-on-Rails implementation for the WebSocket protocol with a number of fallbacks, including an implementation of Flash sockets. It provides a very cool tool for building client-side apps. After all, “WebSocket represents the next evolutionary step in web communication compared to Comet and Ajax.”

 

Installation

If your app is running on any server supporting threads, such as Thin or Puma, you can dive right into using WebSocket by adding gem 'websocket-rails' to your Gemfile. Otherwise, there is an option of using a standalone WebSocket server.

The gem comes with a simple get started generator. Run rails g websocket_rails:install. It will create an events.rb initializer, as well as require JavaScript in your application.js.

To see the gem in development, the official installation instructions advise to enable the config.threadsafe! configuration in development.rb (naturally, it is absolutely necessary for production). Unfortunately, this option prevents the app from reloading the code while the page refreshes.

 

Routes

A WebSocket analog for routes in this gem is called events. A generator creates the events.rb file heavily reminding routes.rb. Its generated version is sparingly commented, and the syntax is quite simple, with subscribe as an analog for match.

WebsocketRails::EventMap.describe do
  namespace :chat_messages do
    subscribe :create, to: Sockets::ChatMessagesController, with_method: :create
  end
end

It doesn’t have any more complex helpers, such as resources, yet. By default, WebSocket controllers don’t enforce REST. So, the create method name appearing later in this article makes me stick to “my comfort zone” conventions. Whether to stick to the show-create-update-destroy scheme is still an open question.

 

Controllers

websocket-rails provides an API that may seem familiar to any Ruby-on-Rails developer. The API features a tree of controllers inherited from a common source and routes-ish events. faye-rails, which is a different take on Faye, clutters routes.rb and requires more configuration. Let’s look at a simple sockets controller.

class Sockets::ChatMessagesController < WebsocketRails::BaseController

  def initialize_session
    @message_sent = 0
  end

  def create
    if can? :create, ChatMessage
      chat_message = ChatMessage.create(event.data) do |message| 
        message.user = current_user
      end
      if chat_message
        @messages_sent += 1
        trigger_success( message: 'Message created' )
        broadcast_message :created, chat_message,
             namespace: :chat_messages
      else
        trigger_failure( message: chat_message.errors.full_messages.join(' ') )
      end
    else
     trigger_failure( message: 'Unauthorized' )
    end
  end

end

First thing we can see above is the fact that WebSocket controllers are inherited from WebsocketRails::BaseController. What differs this controller from the rest of the application is the fact that its instance is created once on the server start, and any WebSocket request is redirected to this very instance. It means that instance variables, such as @messages_sent, are shared across requests.

This controller, in fact, is not related to ActionController, so it doesn’t provide any Ruby-on-Rails controllers’ goodness (redirects, responders, and callbacks) we are accustomed to rely on. Where does can? come from then? websocket-rails uses method_missing to delegate undefined instance methods to DelegationController inherited from ApplicationController. In other words, WebSocket controllers have access to any controller instance helper methods. Adopting any class-enchancing logic, for example, load_and_authorize_resource from the same CanCan gem can be troublesome, though. That prevents us from just plainly reusing the existing controller code. Minor inconvinience at worst, or a problem to solve.

The gem also features a number of responders. A couple of them return action status to a request initializer (trigger_success, and trigger_failure, as well as flash[:status] analogs). A couple more methods initialize events in client JavaScript. send_message affects one client, a requester by default. broadcast_message affects a range of connected clients, all of them by default.

 

The client dispatcher code

Any rich internet application typically features much more client-side code than the server code. websocket-rails applications don’t make an exception, but at least they try. First, we need to open a WebSocket connection from JavaScript.

dispatcher = new WebSocketRails('localhost:3000/websockets');

The gem takes care of mounting the route, and it is the default path on a local server. Note that the request protocol is not specified, it is resolved depending on which one is best supported by the browser. The request sent this way creates an instance of the WebsocketRails::ConnectionManager server side, transporting browser cookies and storing them (so, all the requests through that connection use the same session_id). ConnectionManagers also persist in memory, silently taking care of all the hard work until the server is stopped, or the client closes a connection.

All we need to do now is trigger events as shown below.

...
failure_callback = function(message) { 
	console.log(message); 
}
dispatcher.trigger('chat_messages.create', new_message_object,
	success_callback, failure_callback);

Then, we receive the events.

dispatcher.bind('chat_messages.created', insertNewMessageSomewhere);

 

Even more functionality

Broadcasting events to all clients can be expensive in resources, insecure, etc. Intuition tells, checking user abilities to create each chat message is an overkill, as well. For this purpose, websocket-rails implements the Channel mechanism. Channels feature their own authorization mechanism. So, you can create and destroy these mechanisms in runtime, while subscribing clients to them in JavaScript. This allows, for example, to narrow message broadcasting to channels. Additionally, there exists a possibility to broadcast to specific channels from any place in the rest of the app (ApplicationController descendants, model callbacks, or delayed processess).

Another interesting documented feature is DataStore. WebsocketRails::BaseController instance variables persist between different user requests. So, how to store a data specific for each user/connection? For the purpose, the data_store helper is used. A minor feature, but the same helper can be used to aggregate data from all open connections.

 

Conclusion

After all, it is a library built on an unstable, ‘not-quite-yet-specified-standards’ technology. Nevertheless, it is relatively easy to start using the gem in Ruby-on-Rails. It wraps low-level tinkering with EventMachine, maintaining connections on both sides—server and client. It goes out of its way to be as much compatible with the rest of the application as possible (one can wish for more, though). It even adds some helpers of its own. It is supposedly easy to teach the gem to maintain synchronization between server instances. Last but not the least, it is already thoroughly tested with RSpec and Jasmine.

websocket-rails doesn’t have an extensive set of plugins, or documented best practices, or recipes. It must be quite buggy, too. However, I believe that (provided no better analog appears) it will evolve very fast. It just means that we have an excellent opportunity to influence how these best practices will look like.

 

Further reading