Module: Phlex::Reactive::Component

Extended by:
ActiveSupport::Concern
Includes:
ClientBindings, Identity, Lazy, Streamable
Defined in:
lib/phlex/reactive/component.rb,
lib/phlex/reactive/component/dsl.rb,
lib/phlex/reactive/component/lazy.rb,
lib/phlex/reactive/component/helpers.rb,
lib/phlex/reactive/component/identity.rb,
lib/phlex/reactive/component/registry.rb

Overview

Component turns a self-contained Phlex component into a Livewire-style reactive unit: declare actions in Ruby, and the generic reactive Stimulus controller wires clicks/inputs to an HTTP round trip that re-renders the component and applies it back into the DOM (a plain replace by default; return Response.morph(self) to morph in place and keep the focused input — issue #28). No per-feature Stimulus controllers, no hand-picked Turbo targets.

Including Component pulls in Phlex::Reactive::Streamable automatically (Concern dependency — Streamable lands on the base first, exactly the old manual order), so ONE include is enough. The legacy explicit double include remains a harmless no-op.

Security model (the decisive design choice) ===

We do NOT ship component STATE to the browser (no snapshot). The DOM carries a signed IDENTITY:

* Record-backed (the common case): reactive_record :todo signs the
record's GlobalID. The server re-finds it via GlobalID — the client
can neither forge the component class nor swap the record. State =
the database.
* State-backed (record-less, e.g. a counter): reactive_state :count
signs the listed instance variables. Use when there is genuinely no
record to re-find.
* Both (the inline_edit pattern): reactive_record :record plus
reactive_state :attribute, :editing signs the record's GlobalID AND
the transient mode in one token, so "which field / what mode" survives
every action round trip and stays tamper-proof.

Actions are DEFAULT-DENY: only methods declared with action :name may be invoked. The signature proves the token is ours, NOT that this user may act — your action must still authorize the record. Action params pass through a declared schema; nothing else reaches the method.

Usage (record-backed — #id defaults to dom_id(@todo), issue #81):

class Todos::Item < ApplicationComponent
include Phlex::Reactive::Component

reactive_record :todo
action :toggle
action :rename, params: { title: :string }

def initialize(todo:) = @todo = todo

def toggle  = (authorize!(@todo, :update?); @todo.toggle!(:done))
def rename(title:) = (authorize!(@todo, :update?); @todo.update!(title:))

def view_template
  li(id:, **reactive_attrs) do
    button(**on(:toggle)) { @todo.done? ? "" : "" }
    span { @todo.title }
  end
end
end

Assembled from cohesive concerns (issue #115, restructured in #180) — one include, zero public API change. The include stack, in order:

* Phlex::Reactive::Streamable — the render/broadcast/#id surface (mixed in
first, so its methods sit below the component's own).
* Phlex::Reactive::ClientBindings — the CLIENT-ONLY surface (issue #180),
itself Component::DSL (the declaration registries via Component::Registry
+ from_identity) + Component::Helpers (reply/js, reactive_attrs/root,
on/on_client, the field/select/text bindings, reactive_show/filter/
compute, and the nested-attributes helpers). ClientBindings is the ONE
implementation of that surface, shared with the standalone client-only
include; it carries NO token machinery.
* Component::Identity — reactive_token + the hot-path ivar precomputation.
Its presence is what makes the server-action macros (action/
reactive_record/reactive_state) legal here — they raise on a
ClientBindings-only class that lacks it.
* Component::Lazy — reactive_lazy (deferred initial mount, issue #165).

So a full, token-bearing reactive component is a SUPERSET of a client-only one, not a fork.

Defined Under Namespace

Modules: DSL, Helpers, Identity, Lazy, Registry Classes: Action, CollectionDefinition, ComputeDefinition

Constant Summary

Constants included from Helpers

Helpers::EMPTY_PARAMS_JSON, Helpers::LEGACY_SHOW_CONNECTIVE_KEYS, Helpers::LEGACY_SHOW_PREDICATE_KEYS, Helpers::LISTNAV_ACTIONS, Helpers::OPTIMISTIC_CLASS_OPS, Helpers::SHOW_CONDITION_KEYS

Constants included from DSL

DSL::MIRROR_ID_SELECTOR

Constants included from Streamable

Streamable::BROADCAST_REFUSED_OPS

Method Summary

Methods included from Helpers

#busy_on, #compute_inputs_param, #compute_mirror_param, #js, #mark_authorized!, #nested_attributes, #nested_update!, #on, #on_client, #reactive_attrs, #reactive_compute_attrs, #reactive_connection_id, #reactive_field, #reactive_filter, #reactive_input, #reactive_listnav, #reactive_root, #reactive_select, #reactive_show, #reactive_show_targets, #reactive_text, #reply

Methods included from Streamable

#dom_id, #id, register, registered_classes, reset_all_view_contexts!, #to_stream_morph, #to_stream_remove, #to_stream_replace, #to_stream_token, #to_stream_update