Module: Phlex::Reactive::Component

Extended by:
ActiveSupport::Concern
Includes:
DSL, Helpers, 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 three cohesive concerns (issue #115) — one include, zero public API change:

* Component::DSL      — the five declaration registries (via
Component::Registry) + from_identity
* Component::Identity — reactive_token + the hot-path ivar precomputation
* Component::Helpers  — reply/js, reactive_attrs/root, on/on_client,
the field/select/text bindings, and the nested-attributes helpers

Defined Under Namespace

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

Constant Summary

Constants included from Helpers

Helpers::EMPTY_PARAMS_JSON, Helpers::LISTNAV_ACTIONS, Helpers::OPTIMISTIC_CLASS_OPS, Helpers::SHOW_CONNECTIVE_KEYS, Helpers::SHOW_LITERAL_KEYS, Helpers::SHOW_NUMERIC_KEYS, Helpers::SHOW_PREDICATE_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