Zack Hubert

Ember.js and Rails Part 5, Naive Real-time

Since I’m moving on from Ember for now, here’s one last recipe to almost get some real-time single model functionality with Pusher and Ember.

With Rails, we do our usual Pusher stuff (done this in Backbone recently as well)

Gemfile
1
gem 'pusher'
config/application.rb
1
config.active_record.observers = :pusher_observer
app/models/pusher_observer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class PusherObserver < ActiveRecord::Observer
  observe :post

  def after_create(model)
    channel_name = pusher_channel_name(model)
    Pusher[channel_name].trigger('created',model.to_json)
  end

  def after_update(model)
    channel_name = pusher_channel_name(model)
    Pusher[channel_name].trigger('updated',model.to_json)
  end

  def before_destroy(model)
    channel_name = pusher_channel_name(model)
    Pusher[channel_name].trigger('destroyed',model.to_json)
  end

  def pusher_channel_name(model)
    channel_name = model.class.table_name
  end
end
config/initializers/pusher.rb
1
2
3
Pusher.app_id = '12345'
Pusher.key    = '12345'
Pusher.secret = '12345'

Then on the Ember Side, add the minified pusher JS to ember/lib/ and require it:

app/assets/javascripts/ember/lib/application.js
1
2
//= require ember/lib/pusher.min.js
//= require ember/lib/emberpusher.js
app/assets/javascripts/ember/lib/app.js.coffee
1
App.pusher = new Pusher("abc282882") # replace with app_key
app/assets/javascripts/ember/lib/emberpusher.js.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
( ->
  Emberpusher = (channel, store, model) ->
    @channel = channel
    @store = store
    @model = model

    @channel.bind "created", (pushed_model) =>
      console?.log('created' + JSON.stringify(pushed_model))
      foo = @store.find(@model,pushed_model.id)
      if foo.stateManager?.currentState?.name == 'inFlight'
        # do nothing, same process created it
      else if foo.get('id')
        foo.setProperties(pushed_model)
        foo.stateManager.goToState('loaded')
      else
        @store.load(@model, pushed_model)

    @channel.bind "updated", (pushed_model) =>
      console?.log('updated' + JSON.stringify(pushed_model))
      foo = @store.find(@model,pushed_model.id)
      if foo.get('id') and foo.stateManager?.currentState?.name != 'inFlight'
        foo.setProperties(pushed_model)
        foo.stateManager.goToState('loaded')
      else
        @store.load(@model, pushed_model)

    @channel.bind "destroyed", (pushed_model) =>
      console?.log('destroyed' + JSON.stringify(pushed_model))
      foo = @store.find(@model,pushed_model.id)
      if foo.stateManager?.currentState?.name == 'inFlight'
        # nothing
      else if foo.get('id')
        foo.deleteRecord()

  @Emberpusher = Emberpusher
).call this

As you can see, I’m wrestling with the state machine implementation and I’m not winning. All I wanted to do was say, here’s a new authoritative state for this model, but sometimes it gets into a ‘loading’ or ‘loaded’ state which must be a race condition. Clearly, I shouldn’t be messing with the state machine, or, I could spend more time reading the tests to get all the states and then correct the above but I can’t justify more time spent.

Hopefully this helps someone else carry it across the line.

Credits supertodos

Comments