Skip to content

Instantly share code, notes, and snippets.

@swombat
Created October 15, 2024 07:34
Show Gist options
  • Save swombat/283c86d308373c24f190b62748a6f6c0 to your computer and use it in GitHub Desktop.
Save swombat/283c86d308373c24f190b62748a6f6c0 to your computer and use it in GitHub Desktop.
ActivePage workaround to get cable-like refreshes of complex pages without having a thousand `cable_ready_updates_for` blocks
module ActiveChildren
extend ActiveSupport::Concern
included do
class_attribute :active_children, default: []
after_save :notify_active_children
end
class_methods do
def has_active_child(name)
self.active_children << name
end
def has_active_children(*names)
self.active_children.concat(names)
end
end
def notify_active_children
self.class.active_children.each do |child_name|
children = self.send(child_name)
children.each do |child|
ActionCable.server.broadcast(child.cable_ready_channel_name, { parent_changed: true })
end
end
end
end
class Client < ApplicationRecord
incldue ActiveChildren
has_many :claims, dependent: :destroy, enable_cable_ready_updates: true
has_many :grants, dependent: :destroy, enable_cable_ready_updates: true
has_many :enquiries, dependent: :destroy, enable_cable_ready_updates: true
# Notify all the children in these collections when the client object updates
has_active_children :claims, :grants, :enquiries
end
import { Controller } from "@hotwired/stimulus"
import consumer from "../channels/consumer"
export default class extends Controller {
static targets = ["field"];
static values = {
channel: String,
identifier: String,
clearChannel: String,
debug: Boolean
};
channels = [];
connect() {
this.logIfDebugging("Connecting to ActivePageController");
this.channels.push(consumer.subscriptions.create({
channel: "CableReady::Stream",
identifier: this.channelValue
}, {
received: (data) => {
this.logIfDebugging("Received data:", data);
Turbo.visit(window.location.href, { action: "replace" });
},
connected: () => {
this.logIfDebugging("Successfully connected to stream:", this.channelValue, this.identifierValue);
},
rejected: () => {
this.logIfDebugging("Subscription to stream was rejected:", this.channelValue, this.identifierValue);
},
disconnected: () => {
this.logIfDebugging("Disconnected from stream:", this.channelValue, this.identifierValue);
}
}));
}
disconnect() {
this.logIfDebugging("Disconnecting from ActivePageController");
this.channels.forEach(channel => {
this.logIfDebugging("Unsubscribing from channel:", channel);
channel.unsubscribe();
});
this.logIfDebugging("Disconnected from ActivePageController", this.channelValue, this.clearChannelValue, this.identifierValue);
}
logIfDebugging(...args) {
if (this.debugValue) {
console.log(...args);
}
}
}
module ActivePageHelper
def active_page(object, debug: false)
turbo_frame_tag "active-page-#{object.class.name.underscore}-#{object.id}", refresh: "morph" do
content_tag(:div, "", data: { controller: "active-page", active_page_channel_value: signed_stream_identifier(object.cable_ready_channel_name), active_page_clear_channel_value: object.cable_ready_channel_name, active_page_identifier_value: object.id, active_page_debug_value: debug }) do
yield
end
end
end
end
<%= active_page(@my_object) do %>
...
You may need further turbo-frames inside to delimit things
...
<% end %>
@swombat
Copy link
Author

swombat commented Oct 15, 2024

Oh I forgot that there is also this model method I've added to ApplicationRecord:

  def cable_ready_channel_name(property_name = nil)
    "gid://#{Rails.application.class.module_parent_name.underscore}/#{self.class.name}/#{self.id}#{property_name ? ":#{property_name}" : ""}"
  end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment