Syndicate Rails model updates to your client-side Redux store via Action Cable.
If you have many users viewing the same objects in different sessions, stored in Redux or a similar client-side store, ModelTransporter
allows updates made by one client to flow to the others instantly. Since clients typically update models via web requests, ModelTransporter
batches updates from each request together so that listeners see changes to all related objects at once.
Add this line to your application's Gemfile:
gem 'model_transporter'
Sync updates for a model via:
class MyModel < ApplicationRecord
notifies_model_updates channel: -> { MyChannel.broadcasting_for(self) }
end
The channel
tells ModelTransporter
which listeners to notify, and also serves as a grouping key for updates within a single web request. In the above example, if you had a channel defined as:
class MyChannel < ApplicationCable::Channel
def subscribed
my_model = MyModel.find(params[:id])
stream_for my_model
end
end
Then any client connected to that channel would receive push updates for changes to that model.
If you had a TodoList app with TodoList
objects that were shared between users, Todo
s that belong to TodoList
s, and TodoComment
s that belong to Todo
s, you could set it up as follows to ensure all clients on the same TodoList
page get real-time updates from other users to stay in sync:
class TodoList < ApplicationRecord
has_many :todos, dependent: :destroy
notifies_model_updates channel: -> { TodoListChannel.broadcasting_for(self) }
end
class Todo < ApplicationRecord
belongs_to :todo_list
has_many :todo_comments, dependent: :destroy
notifies_model_updates channel: -> { TodoListChannel.broadcasting_for(todo_list) }
end
class TodoComment < ApplicationRecord
belongs_to :todo
notifies_model_updates channel: -> { TodoListChannel.broadcasting_for(todo.todo_list) }
end
Since all 3 objects use the same parent TodoList
object, any request that updates one or more of these objects at the same time, e.g. deleting a Todo
and its dependent comments, would batch all of those changes and send them to everyone listening on the TodoListChannel
for that TodoList
.
Payloads follow a simple standard format:
{
type: 'server_event/MODEL_UPDATES',
actor_id: ACTOR_ID,
{
creates: {
MODEL_NAME: {
MODEL_ID: MODEL_JSON
},
MODEL_2_NAME: { ... }
}
updates: {
MODEL_NAME: {
MODEL_ID: MODEL_JSON
},
MODEL_2_NAME: { ... }
}
deletes: {
MODEL_NAME: {
MODEL_ID: {}
},
MODEL_2_NAME: { ... }
}
}
}
ModelTransporter
simply sends these messages, by default serializing objects as json by calling as_json
, it is your job to handle them on the client side in the way that makes sense, e.g. by updating objects in your Redux store.
ModelTransporter.configure do |config|
config.actor = :current_user
config.push_adapter = MyPushAdapter.new
end
actor
:ModelTransporter
includes anactor_id
in message payloads, which can be useful if you want to determine who triggered a model update. If you have a controller method calledcurrent_user
, you can setactor
equal to:current_user
, andactor_id
in transporter payloads will get set to that userpush_adapter
: by defaultModelTransporter
assumes you want to send updates viaActionCable
. If you want to send updates in another way, e.g. something likePusher
, set a custompush_adapter
to anything that responds topush_update(channel, message)
. The default action cable push adapter, for reference, is:
# lib/model_transporter/pusher_adapter/action_cable.rb
module ModelTransporter
module PushAdapter
class ActionCable
def push_update(channel, message)
# broadcast can take a coder option as well, which by default is `coder: ActiveSupport::JSON`
::ActionCable.server.broadcast(channel, message)
end
end
end
end
If you want to batch updates outside of a web request, e.g. if you are updating models in a background job, or as a result of messages received over websockets, you can manually batch all updates inside of a block via:
ModelTransporter::BatchModelUpdates.batch_model_updates do
# update multiple models
end
rake spec
The gem is available as open source under the terms of the MIT License.