ModelUpdates
Rails gem to push updates through models into the frontend through ActionCable easily like this:
JavaScript:
ModelUpdates.connectModel("User", userId, "changed-name", function(args) {
console.log("User changed his name to: " + args.new_name)
})
Ruby:
user = User.find(user_id)
user.model_updates_call("changed-name", new_name: "test")
Installation
Add this line to your application's Gemfile:
gem 'model_updates'
And then execute:
$ bundle
Or install it yourself as:
$ gem install model_updates
Include it in your JavaScript:
//= require model_updates
Include the helper in your models:
class ApplicationRecord < ActiveRecord::Base
include ModelUpdates::ModelExtensions
end
Add required CanCan access methods to your ApplicationCable::Channel
:
class ApplicationCable::Channel < ActionCable::Channel::Base
private
# Used to authorize which resources the user can read from (security)
def current_ability
@_current_ability ||= CanCanAbility.new(user: current_user)
end
# Get user from Devise
def current_user
@_current_user ||= env["warden"].user
end
end
In order to get the current user with Devise, you also have to add the following config/initializers/warden_hooks.rb
:
Warden::Manager.after_set_user do |user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = user.id
auth.cookies.signed["#{scope}.expires_at"] = 70.minutes.from_now
end
Choose which attributes should be broadcasted automatically:
class Model < ApplicationRecord
model_updates_broadcast_attributes attributes: [:updated_at]
end
If you also want creates broadcasted:
class Model < ApplicationRecord
model_updates_broadcast_attributes attributes: [:updated_at]
model_updates_broadcast_created
end
Or destroys:
class Model < ApplicationRecord
model_updates_broadcast_attributes attributes: [:updated_at]
model_updates_broadcast_destroyed
end
Usage
Update content on page live automatically
Do like this in your views if you are using HAML to receive automatic updates:
.model-updates{data: {model_updates: model.model_updates_data_attrs(:updated_at)}}
= model.updated_at
Or like this in ERB:
<div class="model-updates" data-model-updates-model="Model" data-model-updates-id="1" data-model-updates-key="updated_at">
<%= model.updated_at %>
</div>
Now that element should update automatically when the model is changed
Callbacks
Updates
You can also do a callback, once the value is changed.
<div class="model-updates" data-model-updates-model="Model" data-model-updates-id="1" data-model-updates-key="updated_at" data-model-updates-callback="myCallback">
<%= model.updated_at %>
</div>
function myCallback(data) {
if (data.value == "something") {
data.element.innerText = "Test: " + data.value
} else {
data.element.innerText = data.value
}
}
The data element is formatted like this:
data = {
changes: "A hash of all the registered changes (multiple attributes might by updated than just the one subscribed to in the same update call)",
element: "Your original element with the class 'model-updates'",
id: "The ID of the model",
key: "The key (attribute name) which was updated",
value: "The new value of the attribute"
}
You can also do this with pure JavaScript instead of tags like this:
ModelUpdates.connectChanged("Task", taskId, function(data) {
$(".task-element").text(data.changes.name)
})
ModelUpdates.update()
Creates
You can receive create callbacks like this:
ModelUpdates.Create.connect({model: "MyModel", onCreated: function(data) {
console.log("New MyModel was created with ID: " + data.id)
})
Destroys
If you want an element automatically removed on destroy:
<div class="model-updates" data-model-updates-model="<%= model.class.name %>" data-model-updates-id="<%= model.id %>" data-model-updates-remove-on-destroy="true">
<%= model.updated_at %>
</div>
You can also manually listen when a model gets destroyed:
ModelUpdates.Destroy.connect({
"id": data.id,
"model": "BuildCommandExecution",
"onDestroyed": function(data) {
console.log("Model destroyed: " + data.id)
}
})
Live updating new elements added dynamically after page load
You can refresh elements with a simple call like this:
ModelUpdates.update()
Events
Call an event on a specific model:
user = User.find(user_id)
user.model_updates_call("changed-name", new_name: "test")
Connect to a specific model:
ModelUpdates.connectModel("User", userId, "changed-name", function(args) {
console.log("User changed his name to: " + args.new_name)
})
Call an event on a model class:
User.model_updates_call("changed-name", new_name: "test")
Connect to events called on a models class:
ModelUpdates.connectModelClass("User", "changed-name", function(args) {
console.log("Someone his name to: " + args.new_name)
})
Call this at the end of your JavaScript, which will actually connect to all the defined events in a batched way:
ModelUpdates.connectEvents()
Debugging
In case you want to enable debug output from ModelUpdates from JavaScript:
ModelUpdates.configuration.debug = true
Contributing
Contribution directions go here.
License
The gem is available as open source under the terms of the MIT License.