CableReady Documentation

repository·main·Indexed 20 days ago

https://github.com/stimulusreflex/cable_ready

CableReady is a bridge between Ruby and the browser that allows server-side code to trigger real-time DOM updates via ActionCable. It provides a standardized JSON wire format and a method-chaining API to simplify real-time web development, reducing the need for custom JavaScript. Version 5.0.6 supports broadcasting to specific Rails resources using Global IDs, implementing reactive components with Stimulus, and utilizing a 'cable car' interface for Ajax responses.

Tokens
43.2K
Snippets
147
Records
179
Agent score
70%

What's inside CableReady

  1. Overview of CableReady

    main

    CableReady enables real-time user experiences by allowing you to trigger client-side DOM changes directly from server-side Ruby. It provides a standardized way to interact with the client via ActionCable web sockets, eliminating the need for writing custom JavaScript for many real-time updates.

    Note: It is recommended to review the official ActionCable documentation to understand the underlying transport mechanism.

  2. What is CableReady?

    main

    CableReady is a tool for creating reactive user experiences by allowing server-side code (e.g., Ruby) to control the browser via JSON payloads. It is language, framework, and transport agnostic; as long as your server can produce JSON, you can use CableReady.

    Key concepts:

    • Operations: Specific instructions sent from the server to the client to perform actions (e.g., DOM mutations, browser manipulations).
    • Broadcasts: Real-time delivery of operations to specific users, everyone online, or custom groups. This enables features like live comments, form validations, and collaborative editing.
  3. What is `cable_car` and when to use it

    main

    While the standard cable_ready method is designed for broadcasting via ActionCable (WebSockets), cable_car is a tool for generating the CableReady operation format (JSON) without being tied to a specific delivery mechanism.

    Use cable_car when you want to:

    • Respond to standard HTTP/Ajax requests.
    • Schedule operations via ActiveJob.
    • Persist operations to a database for later execution.

    Unlike cable_ready, cable_car does not use stream identifiers (no square brackets) and uses dispatch instead of broadcast to generate the operation payload.

    operations = cable_car.inner_html("#users", html: "<b>Users</b>").dispatch
  4. How the `morph` operation works for DOM diffing

    main

    The morph operation performs fast, lightweight DOM diffing/patching without a virtual DOM. It compares the current DOM with new content and applies only the necessary changes.

    Using children_only: true

    When morphing with children_only: true, the provided html must follow these rules to succeed:

    1. It must have a single top-level container element with the same CSS selector as the target.
    2. Inside that container, there must be an element node, not a text node.

    Example for collections: If rendering a collection of partials, wrap the render in a container because the top-level container cannot exist inside each individual partial.

    # Correct way to morph a collection
    morph(
      children_only: true,
      selector: "#bar",
      html: "<div id=\"bar\">" + render(Bar.all) + "</div>"
    )

    Customizing morph via Lifecycle Events

    You can modify operation parameters in the cable-ready:before-morph event. The event.detail.content object provides a direct reference to the internal template element's content (a DocumentFragment).

    Important: You cannot assign a new value to event.detail.content. Instead, mutate the children inside the content using standard DOM APIs:

    document.addEventListener('cable-ready:before-morph', event => {
      event.detail.content.querySelector('#foo').style.color = 'red'
    })

    Parameters:

    • selector (required): CSS selector or XPath expression.
    • html: The HTML to use for morphing.
    • children_only: (Optional) If true, only morphs child nodes, skipping the parent.
    • permanent_attribute_name: (Optional) An attribute name (e.g., data-permanent) that prevents elements from being updated.
    • batch, cancel, delay, focus_selector, select_all, xpath: (See standard operation options)

    Lifecycle Events:

    • cable-ready:before-morph
    • cable-ready:after-morph
    morph(
      selector: "#content",
      html: "<div id='content'>New Content</div>",
      permanent_attribute_name: "data-permanent"
    )
  5. Group operations using Batches

    main

    You can group operations using the batch option, which accepts either true or a string (the batch name).

    Purpose: Batches allow you to detect when a specific group of operations has finished executing, even if the overall broadcast contains other unbatched operations.

    Key Behaviors:

    • When all operations in a batch complete, CableReady emits a cable-ready:batch-complete DOM event on the document.
    • The event's detail object contains a batch key with the batch name.
    • Each operation can belong to at most one batch.
    • Batches do not change the execution order of operations.
    • Batches are reset after every broadcast; they do not persist across multiple broadcasts.
  6. Prevent overwriting active inputs during morphing

    main

    To prevent a jarring user experience where a server update overwrites a text input while a user is typing, CableReady's morph operation includes a built-in shouldMorph callback called verifyNotMutable.

    This callback prevents the server from overwriting input, textarea, and select elements while they currently have focus.

  7. How ActionCable Channel subscriptions work in Rails

    main

    In a standard Rails setup, ActionCable subscriptions are established at the time of the first page load. These subscriptions are created at the page level and are not tied directly to the DOM, meaning they survive Turbo Drive navigation events.

    While common to import CableReady into page-level Channel classes, moving subscriptions into Stimulus controllers provides:

    • Fine-grained control over when subscriptions (and unsubscriptions) occur.
    • Ability to programmatically manipulate subscriptions and the Connection.
    • Flexibility in handling user privilege elevation.

    All Channels share the same memoized Connection, which lives at the page level.

  8. Broadcast CableReady operations to Rails resources using `stream_for` and `broadcast_to`

    main

    Instead of using string-based stream identifiers (e.g., "sailors"), you can broadcast to specific ActiveRecord models using Rails' Global ID functionality. This allows you to shift the mental model from "who are we broadcasting to?" to "what is each individual user interested in?"

    To implement this:

    1. In your ActionCable Channel, use stream_for (or stream_or_reject_for for sensitive data) to subscribe to a specific resource based on params.
    2. On the client, subscribe to the Channel as usual, passing the resource's ID in the params.
    3. Use cable_ready[ChannelClass].dispatch_event.broadcast_to(resource) to send operations to everyone subscribed to that specific resource instance.
    # Server-side: Subscribe to a resource
    class HelensChannel < ApplicationCable::Channel
      def subscribed
        stream_for Helen.find(params[:id])
      end
    end
    
    # Client-side: Subscribe to the channel
    consumer.subscriptions.create({
      channel: 'HelensChannel',
      id: 30
    }, {
      received (data) {
        if (data.cableReady) {
          CableReady.perform(data.operations)
        }
      }
    })
    
    # Server-side: Broadcast to the resource
    helen = Helen.find(30)
    cable_ready[HelensChannel]
      .dispatch_event
      .broadcast_to(helen)
  9. Queue and chain CableReady operations

    main

    CableReady uses a singleton instance for its identifier queues. This allows you to accumulate multiple operations for the same identifier across different parts of your code before finally dispatching them.

    Key Behaviors

    • Accumulation: Calling cable_ready["identifier"] multiple times adds new operations to the existing queue for that identifier.
    • Method Chaining: The CableReady::Channels object returned by cable_ready["identifier"] supports chaining. Operations are broadcast in the exact order they were called.
    • Clearing Queues: The .broadcast method concludes the chain and empties the queues for the dispatched operations.
    • Selectors: Most operations accept a selector to target DOM elements. You can pass the selector as the first argument without a key for brevity.

    Examples

    Accumulating operations:

    # First call adds a log
    cable_ready["visitors"]
      .console_log(message: "We have more salad than we can eat.")
    
    # Second call adds a style change to the same queue
    cable_ready["visitors"]
      .set_style(selector: "body", name: "color", value: "red")
    
    # Finally, send both
    cable_ready["visitors"].broadcast

    Chaining operations:

    # Multiple operations in one chain
    cable_ready["visitors"]
      .console_log(message: "1")
      .console_log(message: "2")
      .broadcast

    Using shorthand selectors:

    # Passing selector as the first positional argument
    cable_ready["visitors"]
      .set_style("#foo", name: "color", value: "blue")
  10. Handle CableReady life-cycle events

    main

    All CableReady operations emit DOM CustomEvents immediately before and after execution.

    • Event Naming: Follows the pattern cable-ready:before-{operation} and cable-ready:after-{operation} (e.g., cable-ready:after-morph).
    • Targeting: Events are emitted from the target selector if provided; otherwise, they default to document.
    • Bubbling: Events bubble and can be cancelled.
    • jQuery Support: If jQuery is present, matching jQuery events are triggered immediately after the DOM events.
    • Exceptions: Operations like dispatch_event and console_log do not emit life-cycle events.

    Note: Do not confuse these life-cycle events with ad-hoc events sent via dispatch_event operations. Life-cycle events are built-in library behaviors.

    // Use named functions for callbacks to ensure they can be removed later
    const afterMorphHandler = event => console.log(event.detail)
    
    document.addEventListener('cable-ready:after-morph', afterMorphHandler)
  11. Understand the CableReady JSON wire format

    main

    CableReady uses a simple, schemaless JSON wire format to communicate instructions from the server to the client. As of v5.0, the format is an array of objects, where each object represents a single operation.

    Key requirements for the JSON structure:

    • Each object must contain an operation key specifying the type of activity.
    • All option keys and values must be camelCased.
    • Operations are schemaless: while they have mandatory and optional standard options, you can pass arbitrary additional options which will be forwarded to the client.
    [
      { "message": "Hello!", "operation": "consoleLog" }
    ]