CableReady
Search…
CableReady Everywhere

Get comfortable with rendering HTML in new places

One thing you'll find yourself doing when working with CableReady is rendering HTML in places you might not previously have done so.
It's possible to use Rails for a while before you realize that the render method might be useful for more than just partials. Perhaps you need to render some JSON or a text string in your controller, but sooner or later you become aware that Rails is calling render for you at the end of most Rails controller actions.
You can call render from almost anywhere, using ApplicationController.render... which just is long enough to get annoying if you type it a lot.
Experience has taught us that shorter calls lead to code that is easier to read and reason about, so we often delegate the render method to ApplicationController so that we don't have to type "ApplicationController" over and over.
In the sections below, you'll learn how to configure ApplicationController, ApplicationRecord and ApplicationJob so that they make working with CableReady a breeze.

Controller Actions

... and API endpoints, web hooks, OAuth callbacks, etc.

First, we recommend that you include CableReady in your ApplicationController:
app/controllers/application_controller.rb
1
class ApplicationController < ActionController::Base
2
include CableReady::Broadcaster
3
end
Copied!
Controller actions that handle Ajax requests, as well as web hooks and OAuth endpoints are great places to call CableReady. It's also common to broadcast CableReady operations to groups of users and/or resources inside controller actions.
If you perform a CableReady broadcast during a standard page controller action, it will send the broadcast immediately; before the action has completed, before the view template has been rendered and before the HTML has been sent to the client. This can lead to developers becoming convinced (incorrectly) that the broadcast did not work.
If you need the user executing the controller action to see the broadcast, you should use an ActiveJob that has been delayed for a few seconds using the set method. There's also a good example of using Stimulus to provide an elegant solution to group update issues.

Ajax

Fans of Hotwire Turbo Streams will be excited to know that it is easy to use CableReady with standard Rails controller actions. Here's how to do it:
1
<%= link_to "Console message", "users/#{current_user.id}/message", method: :patch %>
Copied!
config/routes.rb
1
patch 'users/:id/message', to: 'users#message', constraints: lambda { |request| request.xhr? }
Copied!
app/controllers/users_controller.rb
1
class UsersController < ApplicationController
2
def message
3
cable_ready[UsersChannel].console_log(message: "Hi!").broadcast_to(current_user)
4
head :ok
5
end
6
end
Copied!
Not too shabby, right?

Jobs

Using ActiveJob - especially when it's backed by the awesome Sidekiq - is arguably one of the two best and most common ways developers broadcast CableReady operations, along with Reflexes.
Make sure that CableReady::Broadcaster is included in your ApplicationJob, and delegate render to ApplicationController:
app/jobs/application_job.rb
1
class ApplicationJob < ActiveJob::Base
2
include CableReady::Broadcaster
3
delegate :render, to: :ApplicationController
4
end
Copied!
Here's a genuinely contrived example of using a Job to drive CableReady:
app/views/home/index.html.erb
1
What could possibly happen?<br>
2
<div id="content"></div>
Copied!
app/controllers/home_controller.rb
1
class HomeController < ApplicationController
2
def index
3
ExampleJob.set(wait: 5.seconds).perform_later current_user.id
4
end
5
end
Copied!
If anyone starts lecturing you about the urgent and unquestionable need for the separation of business logic from presentation, tell them that you have work to do.
app/jobs/example_job.rb
1
class ExampleJob < ApplicationJob
2
include CableReady::Broadcaster
3
queue_as :default
4
5
def perform(user_id)
6
user = User.find(user_id)
7
cable_ready[UsersChannel].inner_html(
8
selector: "#content",
9
html: "Hello World this is the background job."
10
).broadcast_to(user)
11
end
12
end
Copied!

ActiveRecord

Make sure that CableReady::Broadcaster is included in your ApplicationRecord, and delegate render to ApplicationController:
app/models/application_record.rb
1
class ApplicationRecord < ActiveRecord::Base
2
self.abstract_class = true
3
include CableReady::Broadcaster
4
delegate :render, to: :ApplicationController
5
6
def sgid
7
to_sgid(expires_in: nil).to_s
8
end
9
end
Copied!
We also recommend that you add a sgid method to your models, to make it easier to work with Secure Global IDs when handling broadcasting to resources. By default, Rails uses the current time to set sgids to expire after a month by default. This means that every time you'd run to_sgid, you would get a different result, which is not useful for our purposes - we need repeatable values.

Callbacks

Some people love them, and some people hate them. Regardless of your feelings about model callbacks, it's hard to ignore how CableReady dances inside of an ActiveRecord callback:
1
class Post < ApplicationRecord
2
after_update do
3
cable_ready[PostsChannel].morph(
4
selector: dom_id(self),
5
html: render(self)
6
).broadcast_to(self)
7
end
8
end
Copied!
If things aren't quite so straight-forward with your partial rendering, you can still do this:
1
class Post < ApplicationRecord
2
after_update do
3
cable_ready[PostsChannel].morph(
4
selector: dom_id(self),
5
html: render(partial: "navbar/posts", locals: { post: self })
6
).broadcast_to(self)
7
end
8
end
Copied!
If the location of your partial needs to be dynamic based on the context, you can re-assign it:
1
class Post < ApplicationRecord
2
after_update do
3
cable_ready[PostsChannel].morph(
4
selector: dom_id(self),
5
html: render(self)
6
).broadcast_to(self)
7
end
8
9
def to_partial_path
10
"navbar/posts"
11
end
12
end
Copied!
All excitement aside, we'd still recommend using those callbacks to queue up ActiveJobs instead of calling CableReady directly. But hey... the more you know, right?

State machines

Another promising use of CableReady inside of your models is state machine transition callbacks:
app/models/post.rb
1
state_machine initial: :pending do
2
event :accept do
3
transition [:pending] => :active
4
end
5
after_transition on: :accept do |post|
6
cable_ready[PostsChannel].morph(
7
selector: dom_id(post),
8
html: render(post)
9
).broadcast_to(post)
10
end
11
end
Copied!

ActionCable

Make sure that CableReady::Broadcaster is included in your ApplicationCable, and delegate render to ApplicationController:
app/channels/application_cable/channel.rb
1
module ApplicationCable
2
class Channel < ActionCable::Channel::Base
3
include CableReady::Broadcaster
4
delegate :render, to: :ApplicationController
5
end
6
end
Copied!
In a new twist, let's empower this channel to receive data from the clients:
app/channels/sailors_channel.rb
1
class SailorsChannel < ApplicationCable::Channel
2
def subscribed
3
stream_from "sailors"
4
end
5
6
def receive(data)
7
cable_ready["sailors"].console_log(message: "A sailor yells: #{data}").broadcast
8
end
9
end
10
Copied!
This controller can send text back up to the server when the greet method is fired:
app/javascript/controllers/sailors_controller.js
1
import { Controller } from 'stimulus'
2
import CableReady from 'cable_ready'
3
4
export default class extends Controller {
5
connect () {
6
this.channel = this.application.consumer.subscriptions.create('SailorsChannel', {
7
received (data) {
8
if (data.cableReady) CableReady.perform(data.operations)
9
}
10
})
11
}
12
13
greet (event) {
14
this.channel.send(event.target.value)
15
}
16
17
disconnect () {
18
this.channel.unsubscribe()
19
}
20
}
Copied!
Finally, let's wire up the input element's change event to the greet method:
index.html.erb
1
<input type="text" data-controller="sailors" data-action="change->sailors#greet">
Copied!

StimulusReflex

StimulusReflex users must not include CableReady::Broadcaster in their Reflex classes, as it makes special versions of the CableReady methods available.
If you would like to read more about using StimulusReflex with CableReady, please consult "Using CableReady inside a Reflex action".
Last modified 4mo ago