Orator Code Climate Build Status Gem Version

Orator is a websocket client/server combination that uses events in order to talk to each other. It is still early in its development.

This project uses web-socket-js as well as em-websocket.

Ruby on Rails Integration

Orator integrates easily with Ruby on Rails. Just add

gem 'orator'

to your Gemfile and bundle install away. In order to use it, you'll need to add the following line to your application.js file:

//= require orator

Now you can use the orator client in your JavaScript! If the browser doesn't support JavaScript, it'll fall back to a flash object.

Server Orators

We'll start with the server-side stuff. A simple rails g orator:config will place the file orator_config.yml in config/, and an example orator in app/orators. Feel free to modify the configuration file (I'll add documentation to it). Let's set up a orator here:

class TestOrator < Orator::Base

This is important. Orator will not accept the Handler unless it is a child of Orator::Base. Orator::Base implements methods that are used by Orator.

  before do
    self.number_of_messages ||= 0
    self.number_of_messages += 1

    if self.number_of_messages > 9_000
      prevent_event
    end
  end

Here we have a basic before block that increments a number every time we get a message. If the number of messages is over 9,000, it will prevent the event from occuring (but any after blocks will still run).

  on :ping
  def ping(data)
    send message('test.pong', message: 'pong')
  end

Here we define a basic event named ping. When the server receives it, it sends back an event named test.pong, with the message pong. Cool, eh?

  on :pong
  def pong(data)
    puts data["message"] # => "pong"
  end

In this we're assuming that somewhere else in our code the client sent us a test.pong event; maybe they did so in response to a ping that we sent. Either way, we're outputting a message that we received.

end

Orator.add_orator(TestOrator)

Here we register our orator with Orator, so it can use it for routing events. But say we also want to handle a socket.open event. Our orator above can't handle that, sadly, so we use this method:

Orator.add_orator do |events|
  events.on('socket.open') do |data|
    self.user = {}

    send mesasge('test.ping', message: 'ping')
  end

  events.on('user.alias') do |data|
    self.user[:name] = json["name"]
  end
end

So when the socket opens in socket.open, we set #user to an empty hash and send a ping message (whose response will be handled by our TestOrator). When we receive the user.alias message, we set the key-pair :name => json["name"] on the #user hash. Cool, eh?

To run the server, run orator --command start in the root rails directory. To daemonize it, run orator --command start -d. To stop, run orator --command stop.

JavaScript Client

Let's go through an example together.

$(document).ready(function() {
  Orator.setup("ws://host:port/path?query", function(events){

Orator.setup handles setting up our socket for us. It also sets up the events and routing, as well. There are some locally defined events that are triggered even if the server hadn't sent them, such as socket.open and socket.close.

  events.on('some.event', function() {})

Here we're binding some event named some.event to an empty function. Boring. Note that the name of the event doesn't matter here.

  events.on('test.ping', function() {
    this.server.send('test.pong', { message: 'pong' });
  });

Here we're responding to a ping that a server might send to this client. We send back a test.pong event, with the message 'pong'. The contents of the event can be anything.

  events.on('test.pong', function(data) {
    console.log(data.message) // => "pong"
  });

Here we received a pong back from the server - this was triggered by the server in response to our ping, which we could have sent at any time.

  events.on('socket.open', function() {
    this.user = {}

    this.server.send('user.alias', { name: some_name })
  });

Here we defined an event that will set up a an empty object on the property user of this. This will become important. We then send the event user.alias to the server (which probably changes the name of the user) with the new name (assuming some_name has a string value).

  events.on('user.alias', function(data) {
    this.user.name = data.name;
  });

It is common place for the server's response events to be named the same as the request events (although the response events may be sent at any time, with no context). Here we see setting the name of the user object to the name the server sent back. This is the same user object that we defined in the event above.

  });

});

And we close our braces.