Class: Phlex::Reactive::Reply

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

Overview

A component-bound facade over Response — the surface an action body uses to control its reply. Component#reply returns one, so an action writes

reply.replace.flash(:error, msg)

instead of

Phlex::Reactive::Response.replace(self).flash(:error, msg)

Two warts disappear: 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 forwards to the Response class method, supplying the bound component as the subject, and returns the real, frozen Response value the endpoint already honors. So immutability, chaining (.flash/.stream/.also_update/.also_replace), and render_self? are inherited untouched — and there is no "verb after a chained Response" asymmetry, because the chain is plain Response all the way down.

Response remains the public value object (it's what the endpoint reads); it is simply an internal detail you rarely name directly now.

Instance Method Summary collapse

Constructor Details

#initialize(component) ⇒ Reply

Returns a new instance of Reply.



34
35
36
# File 'lib/phlex/reactive/reply.rb', line 34

def initialize(component)
  @component = component
end

Instance Method Details

#append(name, model) ⇒ 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).

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

model is the row's record; remove also accepts the row's dom-id string.



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

def append(name, model)
  Response.collection_append(@component, name, model)
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 (Response.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


117
118
119
# File 'lib/phlex/reactive/reply.rb', line 117

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

#morphObject

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



47
48
49
# File 'lib/phlex/reactive/reply.rb', line 47

def morph
  Response.morph(@component)
end

#prepend(name, model) ⇒ Object



71
72
73
# File 'lib/phlex/reactive/reply.rb', line 71

def prepend(name, model)
  Response.collection_prepend(@component, name, model)
end

#redirect(url) ⇒ Object

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



87
88
89
# File 'lib/phlex/reactive/reply.rb', line 87

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

#remove(name = UNSET, model = UNSET) ⇒ Object

Two forms:

reply.remove               # remove the bound component's own element
reply.remove(:items, model) # remove a collection row + count + empty


78
79
80
81
82
# File 'lib/phlex/reactive/reply.rb', line 78

def remove(name = UNSET, model = UNSET)
  return Response.remove(@component) if name.equal?(UNSET)

  Response.collection_remove(@component, name, 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.



42
43
44
# File 'lib/phlex/reactive/reply.rb', line 42

def replace(morph: false)
  Response.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.



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

def streams(*strings)
  Response.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.



54
55
56
# File 'lib/phlex/reactive/reply.rb', line 54

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

#with(*strings) ⇒ Object

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



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

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