Use this in a Rails 3.1 app. Right now the only supported pubsub messaging system is Faye http://faye.jcoglan.com/.
This assumes you already have a Backbone.js + Rails app.
Run a Faye server. It's pretty straightforward, check out
example_faye/run.shin this repo.
Tell your app where the faye server is. This may differ per Rails.env. For now, let's say we add
BackboneSync::Rails::Faye.root_address = 'http://localhost:9292'
//= require extensions/backbone.collection.idempotent //= require backbone_sync-rails/rails_faye_subscriber
(Until I rewrite it, one of 'em uses coffeescript, so you'll need that.)
Open a connection to Faye from your clients, somewhere on your page (in the layout?):
Observe model changes in Rails, and broadcast them. We provide the guts of an observer for you, so add a file like
class UserObserver < ActiveRecord::Observer include BackboneSync::Rails::Faye::Observer end
and enable it in
config/application.rblike any good observer:
module MyApp class Application < Rails::Application # snip... # Activate observers that should always be running. config.active_record.observers = :user_observer # snip... end end
Instantiate a new
BackboneSync.RailsFayeSynchronizerfor each instance of a Backbone collection you instantiate. You could do this in the collection's constructor, or do it by hand:
// For simplicitly, here it is in a router, or app bootstrap this.users = new MyApp.Collections.UsersCollection() new BackboneSync.RailsFayeSubscriber(this.users, channel: 'users') this.wizards.reset options.users
Check it out! Open two browsers, do some stuff in one, and see your changes cascade to the other. Your Backbone views will need to observe events on the collection like
Installing on Rails < 3.1
If you're on a version of Rails < 3.1, you'll probably have to copy some files
into your app by hand, like the
vendor/assets files. You'll probably have to
lib/backbone_sync-rails/faye.rb file yourself, too.
In short, I augment the
Backbone.Collection.prototype._add function so
that adding multiple models to the same collection with the same
idAttribute-specified attribute of choice) will pass silently.
In a distributed messaging system, messages should be idempotent: this means that, for any message, an actor should be able to execute that message several times with no ill effect.
Why? Consider the following situation.
- There are two clients, Alice and Bob.
- Alice creates a new model in Backbone.
- The server receives her request and persists it. It also distributes a
"create" message to all subscribed clients. 4. Alice's new model is added to
her local collection in the normal due course of
- Bob receives the create message and creates a model in his local collection.
- All is well until this point. Now, Alice receives the create message (she is subscribed just as Bob is) and creates a duplicate model into her collection.
There is actually a race condition in that Alice's HTTP request to create (and
therefore her normal
save()-based addition to the collection)_ may complete
before or after the pubsub notification informs her collection to add a new
One approach to solving this would be for each update message to be tagged with
its originating client, and for each client to filter out those messages. This
would prove difficult, particularly since, in this implementation, the
ActiveModel::Observer subclass is decoupled from the originating client.
The change made in
Backbone.Collection.prototype.add idempotent with respect to the
id attribute, and neatly addresses the issue.
I'm more than happy to hear about better approaches from people with more experience in distributed messaging systems.
Copyright (c) 2011 Jason Morrison. See MIT-LICENSE for details.