Class: Phlex::Reactive::Reply

Inherits:
Object
  • Object
show all
Defined in:
lib/phlex/reactive/reply.rb

Overview

The ONE door for building an action's reply (issue #182). Component#reply returns one, so an action writes

reply.replace.flash(:error, msg)

There is no constant to qualify (reply is a method, resolved on the component — so a namespaced component needs no Response = … alias) and no self to thread (the component is bound).

Reply is NOT a Response and does NOT subclass one: each verb calls the Response build_* class method, supplying the bound component as the subject, and returns the real, frozen Response value the endpoint honors. So immutability, chaining (.flash/.stream/.also/.js/.defer), and render_self? come from the Response value object untouched — the chain is plain Response all the way down.

Response remains the value object the endpoint reads; you never construct it directly (its former public class verbs raise a guided rewrite).

Instance Method Summary collapse

Constructor Details

#initialize(component) ⇒ Reply

Returns a new instance of Reply.



24
25
26
# File 'lib/phlex/reactive/reply.rb', line 24

def initialize(component)
  @component = component
end

Instance Method Details

#append(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object

Reactive collections (issue #35) — add/remove a row in a declared reactive_collection, emitting the row stream + the count companion + the empty-state toggle as ONE Response. The bound component is the container (it carries the declaration + size resolver). The collection is named by the to:/from: keyword (issue #182 — reads as Ruby, and a to: present unambiguously means "a collection row"):

def add_item(...)    = (item = @list.items.create!(...); reply.append(item, to: :items))
def remove_item(id:) = (@list.items.find(id).destroy!;   reply.remove(id, from: :items))

model is the row's record; remove also accepts the row's dom-id string. Extra kwargs (issue #186) thread to the row component's new(model:, **row_kwargs) — e.g. reply.append(item, to: :items, autofocus: true) → ItemRow.new(item:, autofocus: true). Additive: a no-kwarg call is byte-identical to before.



62
63
64
# File 'lib/phlex/reactive/reply.rb', line 62

def append(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs)
  collection_build!(:build_collection_append, :append, model, legacy_model, to, row_kwargs)
end

#defer(component, placeholder: nil, morph: false) ⇒ Object

Defer an expensive segment (issue #165): reply.defer(Totals.new(...)).

With NO prior verb, the bound component's token is refreshed via a tiny token-only stream (reply.streams), NOT a full self-replace: a full replace would re-render the acting component SYNCHRONOUSLY on the request thread — and if you defer your OWN subject (reply.defer(self)), that is the very render you meant to move OFF the critical path, so the defer would be silently defeated (a double render). The token-only refresh rolls the signed identity forward without any synchronous render; the deferred segment's directive rides alongside. Chain off a verb when you DO want a synchronous piece too:

reply.streams(volume_cell).defer(SessionTotals.new(workout: @workout))
reply.replace.defer(Totals.new(order: @order))   # explicit self re-render


123
124
125
# File 'lib/phlex/reactive/reply.rb', line 123

def defer(component, placeholder: nil, morph: false)
  Response.build_streams(@component).defer(component, placeholder:, morph:)
end

#morphObject

Re-render in place via Idiomorph (method="morph") — keeps focus + caret.



37
38
39
# File 'lib/phlex/reactive/reply.rb', line 37

def morph
  Response.build_morph(@component)
end

#prepend(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs) ⇒ Object



66
67
68
# File 'lib/phlex/reactive/reply.rb', line 66

def prepend(model = UNSET_ARG, legacy_model = UNSET_ARG, to: UNSET_ARG, **row_kwargs)
  collection_build!(:build_collection_prepend, :prepend, model, legacy_model, to, row_kwargs)
end

#redirect(url) ⇒ Object

Client-side full navigation (Turbo.visit). Pass a *_url.



93
94
95
# File 'lib/phlex/reactive/reply.rb', line 93

def redirect(url)
  Response.build_redirect(url)
end

#remove(model = UNSET_ARG, legacy_model = UNSET_ARG, from: UNSET_ARG) ⇒ Object

Two forms, dispatched on the PRESENCE of from: (issue #182 — no sentinel overload): bare reply.remove removes the bound component's own element; reply.remove(model, from: :items) removes a collection row + count + empty-state. model may be the record or the row's dom-id string.

reply.remove                       # remove the bound component's element
reply.remove(model, from: :items)  # remove a collection row


77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/phlex/reactive/reply.rb', line 77

def remove(model = UNSET_ARG, legacy_model = UNSET_ARG, from: UNSET_ARG)
  reject_legacy_form!(:remove, model, legacy_model)
  # Bare `reply.remove` (no positional, no from:) removes self.
  return Response.build_remove(@component) if from.equal?(UNSET_ARG) && model.equal?(UNSET_ARG)

  if from.equal?(UNSET_ARG)
    raise ArgumentError,
      "reply.remove(model, …) needs a source collection — reply.remove(model, from: :name)"
  end
  reject_symbol_first!(:remove, model, :from, from)
  Response.build_collection_remove(@component, from, resolve_row_arg(model))
end

#replace(morph: false) ⇒ Object

Re-render in place. morph: true morphs the subtree (preserves the focused input + caret) instead of an outerHTML swap — see #morph.



32
33
34
# File 'lib/phlex/reactive/reply.rb', line 32

def replace(morph: false)
  Response.build_replace(@component, morph:)
end

#streams(*strings) ⇒ Object

Self-targeting again: emit exactly these streams with a TOKEN-ONLY refresh (issue #30) — partial/per-field update, NO full-self replace, so the component's live inputs survive. The bound component supplies the token.



105
106
107
# File 'lib/phlex/reactive/reply.rb', line 105

def streams(*strings)
  Response.build_streams(@component, *strings)
end

#update(morph: false) ⇒ Object

Update only inner HTML (preserves the root element + its token attr). morph: true morphs the inner HTML in place (issue #113) so a cross-tab/per-field update keeps a focused input's caret.



44
45
46
# File 'lib/phlex/reactive/reply.rb', line 44

def update(morph: false)
  Response.build_update(@component, morph:)
end

#with(*strings) ⇒ Object

Escape hatch / multi-stream root: zero or more raw turbo-stream strings.



98
99
100
# File 'lib/phlex/reactive/reply.rb', line 98

def with(*strings)
  Response.build_with(*strings)
end