#316 Private Pub
- Download:
- source codeProject Files in Zip (80.9 KB)
- mp4Full Size H.264 Video (15.2 MB)
- m4vSmaller H.264 Video (8.28 MB)
- webmFull Size VP8 Video (10 MB)
- ogvFull Size Theora Video (17.4 MB)
In episode 260 we showed how you can update a user’s browser in real time using Faye. While Faye is great it can take quite a bit of work to set up especially if we want it fully secured and locked down. This motivated Ryan Bates to create a gem called Private Pub which is built on top of Faye. His goal is to make it even easier to publish and subscribe to real-time events in a Rails application and in this episode we’ll show you how it works.
The application we’ll be working with is a simple chat application. If we enter a message in the text field at the bottom of the page and click “Send” the browser will send an AJAX request to save the message then update the browser to add the message to the main chat window.
There’s a problem with our application, however. If there are multiple chat clients open they won’t all be updated in real time when a message is sent. Only the client that sent the message will see the new message; all the others will have to reload the page to see the it. There are a couple of solutions to this problem. One is to have each client poll for new messages every couple of seconds of so, which isn’t the best idea. The other is to hold a socket connection open to the server on each client so that the server can push new messages as they come in so that they appear in real time. The problem with this solution is that Rails’ architecture isn’t set up to to handle long requests.
Faye and Private Pub can help with this but before we start looking at these we’ll show you how our chat application works. We’ll start with the view template for the chat page.
<h1>Chat</h1> <ul id="chat"> <%= render @messages %> </ul> <%= form_for Message.new, remote: true do |f| %> <%= f.text_field :content %> <%= f.submit "Send" %> <% end %>
This template renders a partial called messages
to render the list of messages and has a form for creating new messages. The form uses the remote: true
option so that it’s submitted via an AJAX request. When the form is submitted it is sent to the MessagesController
’s create
action; let’s take a look at that next.
class MessagesController < ApplicationController def index @messages = Message.all end def create @message = Message.create!(params[:message]) end end
The create
action is very simple. It creates a new Message
record but notice that there’s no response as this action will render out a JavaScript template as it’s called through AJAX. Here’s what that JavaScript template looks like.
$("#chat").append("<%= j render(@message) %>");
$("#new_message")[0].reset();
This JavaScript appends the newly-created message to the chat list and clears the new message text field. What we want it to do is broadcast this message to all the chat clients so that they all display the new message, not just the one that sent it. We can use Private Pub to help us do this.
Installing Private Pub
Private Pub is installed in the usual way. We add the gem to the application’s gemfile and then run bundle
. Installing it will also install Faye and its dependencies.
source 'http://rubygems.org' gem 'rails', '3.1.3' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.5' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'private_pub'
Next we need to run this generator to generate the configuration file and a Rackup file for starting the Faye server.
$ rails g private_pub:install create config/private_pub.yml create private_pub.ru
We can start up that Rack server by running this command:
$ rackup private_pub.ru -s thin -E production >> Thin web server (v1.3.1 codename Triple Espresso) >> Maximum connections set to 1024 >> Listening on 0.0.0.0:9292, CTRL+C to stop
This command starts up Faye using the Thin server in the production environment which is necessary to get Faye working. The last step is to add private_pub
to the application’s JavaScript manifest file.
// This is a manifest file that'll be compiled into including all the files listed below. // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. // //= require jquery //= require jquery_ujs //= require private_pub //= require_tree .
If your application uses a version of Rails earlier than 3.1 you can include this JavaScript file in your layout file.
Using Private Pub in Our Applications
Now that we have Private Pub set up it’s easy to subscribe to a channel and publish update notifications to that channel. All we have to do is subscribe to a channel in a view template, such as our messages page, by calling subscribe_to
and passing in the name of a channel. The format of a Faye channel looks like a path and here we’re going to use /messages/new
.
<h1>Chat</h1> <ul id="chat"> <%= render @messages %> </ul> <%= form_for Message.new, remote: true do |f| %> <%= f.text_field :content %> <%= f.submit "Send" %> <% end %> <%= subscribe_to "/messages/new" %>
Next we need to go into a JavaScript template that renders as a result of an AJAX request, such as the one we have that renders each new chat message, and wrap its code in a publish_to
block.
<% publish_to "/messages/new" do %> $("#chat").append("<%= j render(@message) %>"); $("#new_message")[0].reset(); <% end %>
Doing this means that this JavaScript won’t just be returned to the client that made the request but will be published out to all the subscribers to the /messages/new
channel. We can try this out now. If we open our application in two separate browsers and enter a message in one of them it will instantly appear in the other.
We might not always want to publish to a channel directly from an AJAX response. For example we might instead want to publish from a controller action. We can do this anywhere in Ruby code by using PrivatePub.publish_to
, passing in the name of the channel we want to publish to and the JavaScript code we want to run on every subscribing client. We’ll demonstrate this by returning an alert
.
def create @message = Message.create!(params[:message]) PrivatePub.publish_to("/messages/new", "alert('#{@message.content}');") end
Now when we type in a message in one browser we see an alert
in both browsers.
Working With JSON Data
If you prefer to work with JSON data instead of JavaScript code we can do that too. All we need to do is pass an object other than a string to publish_to
, such as a hash, and it will be converted to JSON.
def create @message = Message.create!(params[:message]) PrivatePub.publish_to("/messages/new", message: @message) end
If we use this approach we’ll need to write some JavaScript to handle the JSON that’s returned. We’ll write this in the messages
CoffeeScript file.
PrivatePub.subscribe "/messages/new", (data, channel) -> alert data.message.content
In this file we call PrivatePub.subscribe
. This takes a channel name and a callback function which takes data
and channel
arguments. The callback is triggered every time the channel receives some JSON data. In our code we just alert
the returned data’s message.content
. When we reload the page now and enter another message we’ll see the alert based on the JSON data that’s returned from the server.
One advantage to using Private Pub is that channels are private by default. This means that we don’t need to worry about users listening to channels that we haven’t explicitly subscribed them to. This makes it easy to create, for example, a private chatroom where only the users who have been subscribed to its channel can read the messages. We can also choose to expire subscriptions after a given time so that they users can’t listen in after they’ve logged out. Every Rails application that uses Private Pub will have a private_pub.yml
file where we can configure it.
development: server: "http://localhost:9292/faye" secret_token: "secret" test: server: "http://localhost:9292/faye" secret_token: "secret" production: server: "http://example.com/faye" secret_token: "210eb617b6ce1c351d986a3185d34025cf42e5091a37502f18f595f7e8773853" signature_expiration: 3600 # one hour