phlex-reactive 0.12.6 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -2
- data/README.md +268 -16
- data/app/controllers/phlex/reactive/actions_controller.rb +15 -1
- data/app/javascript/phlex/reactive/compute.min.js +2 -2
- data/app/javascript/phlex/reactive/compute.min.js.map +1 -1
- data/app/javascript/phlex/reactive/confirm.min.js +2 -2
- data/app/javascript/phlex/reactive/confirm.min.js.map +1 -1
- data/app/javascript/phlex/reactive/confirm_predicate.min.js +2 -2
- data/app/javascript/phlex/reactive/confirm_predicate.min.js.map +1 -1
- data/app/javascript/phlex/reactive/inspect.min.js +2 -2
- data/app/javascript/phlex/reactive/inspect.min.js.map +1 -1
- data/app/javascript/phlex/reactive/reactive_controller.js +139 -18
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/lib/phlex/reactive/collections.rb +144 -0
- data/lib/phlex/reactive/component/helpers.rb +5 -0
- data/lib/phlex/reactive/pending.rb +337 -0
- data/lib/phlex/reactive/reply.rb +37 -0
- data/lib/phlex/reactive/response.rb +109 -68
- data/lib/phlex/reactive/settle.rb +170 -0
- data/lib/phlex/reactive/settles.rb +272 -0
- data/lib/phlex/reactive/streamable.rb +146 -3
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +35 -0
- metadata +5 -1
|
@@ -119,87 +119,64 @@ module Phlex
|
|
|
119
119
|
# `effect:` (issue #215) stamps the ROW stream only — the count
|
|
120
120
|
# companion and the empty-state toggle are bookkeeping, not the thing
|
|
121
121
|
# entering/leaving.
|
|
122
|
+
# Issue #248: the bookkeeping BODIES moved to Phlex::Reactive::Collections
|
|
123
|
+
# so the job-side settle and the peers broadcast run the SAME code. These
|
|
124
|
+
# builders keep their exact behaviour by delegating.
|
|
122
125
|
def build_collection_append(component, name, model, effect: nil, **row_kwargs)
|
|
123
|
-
definition =
|
|
124
|
-
new(streams:
|
|
125
|
-
render_self: false, token_component: component)
|
|
126
|
+
definition = Phlex::Reactive::Collections.definition!(component, name)
|
|
127
|
+
new(streams: Phlex::Reactive::Collections.add_streams(definition, component, model, :append, row_kwargs,
|
|
128
|
+
effect:), render_self: false, token_component: component)
|
|
126
129
|
end
|
|
127
130
|
|
|
128
131
|
def build_collection_prepend(component, name, model, effect: nil, **row_kwargs)
|
|
129
|
-
definition =
|
|
130
|
-
new(streams:
|
|
131
|
-
render_self: false, token_component: component)
|
|
132
|
+
definition = Phlex::Reactive::Collections.definition!(component, name)
|
|
133
|
+
new(streams: Phlex::Reactive::Collections.add_streams(definition, component, model, :prepend, row_kwargs,
|
|
134
|
+
effect:), render_self: false, token_component: component)
|
|
132
135
|
end
|
|
133
136
|
|
|
134
137
|
def build_collection_remove(component, name, model, effect: nil)
|
|
135
|
-
definition =
|
|
136
|
-
new(streams:
|
|
138
|
+
definition = Phlex::Reactive::Collections.definition!(component, name)
|
|
139
|
+
new(streams: Phlex::Reactive::Collections.remove_streams(definition, component, model, effect:),
|
|
137
140
|
render_self: false, token_component: component)
|
|
138
141
|
end
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
# Row remove + count + empty-state restore. The empty-state is appended
|
|
165
|
-
# back into the container only when the list just emptied (size == 0).
|
|
166
|
-
def collection_remove_streams(definition, component, model, effect: nil)
|
|
167
|
-
streams = [collection_row_remove(definition, model, effect)]
|
|
168
|
-
append_count_stream(streams, definition, component)
|
|
169
|
-
|
|
170
|
-
size = definition.size_for(component)
|
|
171
|
-
if definition.empty && size&.zero?
|
|
172
|
-
# Render the empty-state and append it INTO the container (not its
|
|
173
|
-
# own id) — restoring "No items yet" when the last row was removed.
|
|
174
|
-
# model: nil builds it argument-free (an empty-state is a static view).
|
|
175
|
-
streams << definition.empty.append(target: definition.container, model: nil)
|
|
143
|
+
# --- Async-action lifecycle (issue #248) ---
|
|
144
|
+
# Mark targets pending and hand the fulfilment to the app's own job. The
|
|
145
|
+
# reply deliberately does NOT re-render the container: re-rendering it
|
|
146
|
+
# here would draw the PRE-JOB world (the endpoint renders inside the
|
|
147
|
+
# transaction; the queue publishes on commit) — the exact bug this verb
|
|
148
|
+
# exists to fix. render_self is therefore false, with the container as
|
|
149
|
+
# token_component so the signed token still rolls forward (cosmos#1939 —
|
|
150
|
+
# without it the list is act-once-only).
|
|
151
|
+
#
|
|
152
|
+
# The Response only RECORDS the segment; the ENDPOINT turns it into the
|
|
153
|
+
# marker + directive streams after the transaction committed.
|
|
154
|
+
# Pull `in:` out of reply.pending's **opts and REFUSE anything left over.
|
|
155
|
+
# `in` is a Ruby keyword, so it cannot be a named parameter — which means
|
|
156
|
+
# every other keyword lands in **opts and would otherwise be silently
|
|
157
|
+
# dropped. A typo (`jbo:` for `job:`) would then mark the rows pending
|
|
158
|
+
# and enqueue NOTHING: a permanently shimmering row, the exact failure
|
|
159
|
+
# this feature exists to prevent. So it fails at the call site instead.
|
|
160
|
+
def pending_collection!(opts)
|
|
161
|
+
collection = opts.delete(:in)
|
|
162
|
+
unless opts.empty?
|
|
163
|
+
raise ArgumentError,
|
|
164
|
+
"reply.pending got unknown keyword(s) #{opts.keys.map(&:inspect).join(", ")} — " \
|
|
165
|
+
"it takes in:, job:, args:, peers: and a block. A dropped keyword would mark the " \
|
|
166
|
+
"targets pending with nothing to settle them."
|
|
176
167
|
end
|
|
177
|
-
streams
|
|
178
|
-
end
|
|
179
168
|
|
|
180
|
-
|
|
181
|
-
# or an already-built dom-id string (e.g. the value the row used as #id).
|
|
182
|
-
def collection_row_remove(definition, model, effect = nil)
|
|
183
|
-
if model.is_a?(String)
|
|
184
|
-
Phlex::Reactive::Effects.annotate(Phlex::Reactive.stream_builder.remove(model), effect)
|
|
185
|
-
else
|
|
186
|
-
definition.item.remove(model, effect:)
|
|
187
|
-
end
|
|
169
|
+
collection
|
|
188
170
|
end
|
|
189
171
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return if size.nil?
|
|
197
|
-
|
|
198
|
-
streams << update_stream(definition.count, size.to_s)
|
|
172
|
+
def build_pending(component, records, collection:, peers:, job:, args:, enqueue:)
|
|
173
|
+
segment = Phlex::Reactive::Pending.build_segment(
|
|
174
|
+
component, records, collection:, peers:, job:, args:, enqueue:
|
|
175
|
+
)
|
|
176
|
+
new(streams: [], render_self: false, token_component: component,
|
|
177
|
+
pending_segments: segment ? [segment] : NO_SEGMENTS)
|
|
199
178
|
end
|
|
200
179
|
|
|
201
|
-
public
|
|
202
|
-
|
|
203
180
|
# Partial / per-field update with a TOKEN-ONLY refresh (issue #30). Emits
|
|
204
181
|
# EXACTLY the given streams — no forced full-self replace — but binds
|
|
205
182
|
# `component` so the endpoint appends its tiny `to_stream_token` stream.
|
|
@@ -376,13 +353,14 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
376
353
|
NO_SEGMENTS = [].freeze
|
|
377
354
|
|
|
378
355
|
def initialize(streams: [], redirect_url: nil, render_self: true, token_component: nil,
|
|
379
|
-
subject_component: nil, deferred_segments: NO_SEGMENTS)
|
|
356
|
+
subject_component: nil, deferred_segments: NO_SEGMENTS, pending_segments: NO_SEGMENTS)
|
|
380
357
|
@streams = streams.freeze
|
|
381
358
|
@redirect_url = redirect_url
|
|
382
359
|
@render_self = render_self
|
|
383
360
|
@token_component = token_component
|
|
384
361
|
@subject_component = subject_component
|
|
385
362
|
@deferred_segments = deferred_segments.freeze
|
|
363
|
+
@pending_segments = pending_segments.freeze
|
|
386
364
|
freeze
|
|
387
365
|
end
|
|
388
366
|
|
|
@@ -394,6 +372,13 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
394
372
|
|
|
395
373
|
def deferred? = !@deferred_segments.empty?
|
|
396
374
|
|
|
375
|
+
# The recorded reply.pending segments (issue #248), in call order. Same
|
|
376
|
+
# contract as deferred_segments: recorded here, turned into wire streams
|
|
377
|
+
# by the endpoint AFTER the transaction committed.
|
|
378
|
+
attr_reader :pending_segments
|
|
379
|
+
|
|
380
|
+
def pending? = !@pending_segments.empty?
|
|
381
|
+
|
|
397
382
|
# Append extra turbo-stream strings (a sibling component, a flash).
|
|
398
383
|
# Returns a NEW Response (immutable).
|
|
399
384
|
def stream(*more)
|
|
@@ -403,7 +388,8 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
403
388
|
render_self: @render_self,
|
|
404
389
|
token_component: @token_component,
|
|
405
390
|
subject_component: @subject_component,
|
|
406
|
-
deferred_segments: @deferred_segments
|
|
391
|
+
deferred_segments: @deferred_segments,
|
|
392
|
+
pending_segments: @pending_segments
|
|
407
393
|
)
|
|
408
394
|
end
|
|
409
395
|
|
|
@@ -434,7 +420,52 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
434
420
|
token_component: @token_component,
|
|
435
421
|
subject_component: @subject_component,
|
|
436
422
|
deferred_segments: @deferred_segments +
|
|
437
|
-
[Phlex::Reactive::Defer::Segment.new(component:, placeholder:, morph:)]
|
|
423
|
+
[Phlex::Reactive::Defer::Segment.new(component:, placeholder:, morph:)],
|
|
424
|
+
pending_segments: @pending_segments
|
|
425
|
+
)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Chain reply.pending onto an existing reply (issue #248), so an action can
|
|
429
|
+
# do one synchronous thing AND mark other targets pending:
|
|
430
|
+
#
|
|
431
|
+
# reply.replace.pending(rows, in: :declined, job: RestoreJob)
|
|
432
|
+
#
|
|
433
|
+
# Dead on a redirect, exactly like #defer: the client is navigating away,
|
|
434
|
+
# so the settle could never land.
|
|
435
|
+
def pending(records, peers: false, job: nil, args: nil, **opts, &enqueue)
|
|
436
|
+
if redirect?
|
|
437
|
+
raise Phlex::Reactive::Error,
|
|
438
|
+
"reply.pending on a redirect reply is dead — the client is navigating away, " \
|
|
439
|
+
"so the settle could never land"
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
subject = pending_subject!
|
|
443
|
+
# ONE subscription per anchor. Every pending segment on the same
|
|
444
|
+
# container emits a directive targeting that container's id, and the
|
|
445
|
+
# client keys its in-flight subscriptions BY TARGET — so a second
|
|
446
|
+
# directive supersedes the first, silently orphaning the jobs the first
|
|
447
|
+
# call enqueued. Two pending calls on one container in one reply is a
|
|
448
|
+
# call-site mistake; make it a loud one.
|
|
449
|
+
if @pending_segments.any? { it.handle.anchor == subject.id }
|
|
450
|
+
raise Phlex::Reactive::Error,
|
|
451
|
+
"reply.pending was called twice for #{subject.class} (##{subject.id}) — the second " \
|
|
452
|
+
"subscription would supersede the first on the client, so the first call's jobs could " \
|
|
453
|
+
"never settle. Mark every target in ONE reply.pending call (the settle names its own " \
|
|
454
|
+
"collection: s.remove(record, from: :name))."
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
built = self.class.build_pending(
|
|
458
|
+
subject, records, collection: self.class.pending_collection!(opts),
|
|
459
|
+
peers:, job:, args:, enqueue:
|
|
460
|
+
)
|
|
461
|
+
self.class.new(
|
|
462
|
+
streams: @streams,
|
|
463
|
+
redirect_url: @redirect_url,
|
|
464
|
+
render_self: @render_self,
|
|
465
|
+
token_component: @token_component || built.token_component,
|
|
466
|
+
subject_component: @subject_component,
|
|
467
|
+
deferred_segments: @deferred_segments,
|
|
468
|
+
pending_segments: @pending_segments + built.pending_segments
|
|
438
469
|
)
|
|
439
470
|
end
|
|
440
471
|
|
|
@@ -537,6 +568,16 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
537
568
|
|
|
538
569
|
private
|
|
539
570
|
|
|
571
|
+
# The container a chained .pending marks: the component this reply is
|
|
572
|
+
# already bound to. A subject-free reply (reply.with) has none — chaining
|
|
573
|
+
# .pending onto it is a call-site mistake, so it fails loudly.
|
|
574
|
+
def pending_subject!
|
|
575
|
+
(@subject_component || @token_component) ||
|
|
576
|
+
raise(Phlex::Reactive::Error,
|
|
577
|
+
"reply.pending needs a bound component (the container that owns the collection) — " \
|
|
578
|
+
"chain it off a component verb (reply.replace.pending(...)) or call reply.pending(...) directly")
|
|
579
|
+
end
|
|
580
|
+
|
|
540
581
|
# The default `target` for #js: the bound component's id when this reply is
|
|
541
582
|
# component-scoped (replace/morph/update set subject_component; .streams and
|
|
542
583
|
# collections set token_component), else nil — a subject-free reply.with is
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phlex
|
|
4
|
+
module Reactive
|
|
5
|
+
# The job-side reply builder (issue #248) — what `reactive_settle` yields.
|
|
6
|
+
#
|
|
7
|
+
# reactive_settle do |s|
|
|
8
|
+
# if result.success?
|
|
9
|
+
# s.remove(transfer, from: :unreconcilable)
|
|
10
|
+
# else
|
|
11
|
+
# s.replace(transfer)
|
|
12
|
+
# s.flash(:alert, result.error)
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# The verbs mirror `reply.*` and, critically, route through the SAME
|
|
17
|
+
# Phlex::Reactive::Collections decisions — so a job-side append emits the
|
|
18
|
+
# row AND the count companion AND the 0<->1 empty-state toggle, instead of
|
|
19
|
+
# the bare row `broadcast_to(append:)` gives you. That shared bookkeeping is
|
|
20
|
+
# the whole point: it is what the app can no longer get subtly wrong.
|
|
21
|
+
#
|
|
22
|
+
# Unlike Response, Settle is a MUTABLE accumulator, not a value object: a
|
|
23
|
+
# settle block is imperative (branch on the work's result, add a flash) and
|
|
24
|
+
# every verb returns self so the calls can also be chained.
|
|
25
|
+
#
|
|
26
|
+
# It collects streams; Phlex::Reactive::Settles owns the delivery — one
|
|
27
|
+
# durable message to the actor's shared one-shot stream, plus the optional
|
|
28
|
+
# peers broadcast.
|
|
29
|
+
class Settle
|
|
30
|
+
attr_reader :streams
|
|
31
|
+
|
|
32
|
+
# The rebuilt container — Settles reads it for the peers broadcast (it
|
|
33
|
+
# carries the collection declaration and the size resolver).
|
|
34
|
+
attr_reader :container
|
|
35
|
+
|
|
36
|
+
# `container` is the rebuilt container component (from_identity, off the
|
|
37
|
+
# request thread); `handle` is the Pending::Handle the enqueue captured.
|
|
38
|
+
def initialize(container, handle)
|
|
39
|
+
@container = container
|
|
40
|
+
@handle = handle
|
|
41
|
+
@streams = []
|
|
42
|
+
@peer_ops = []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Re-render ONE row in place. The work ran and the row is simply different
|
|
46
|
+
# now (a failed re-execution going back to actionable, say). No count or
|
|
47
|
+
# empty-state stream: a replace cannot change the size.
|
|
48
|
+
#
|
|
49
|
+
# Accepts a record (resolved through the collection's row component) or a
|
|
50
|
+
# built Streamable component (its own #id is the target).
|
|
51
|
+
def replace(model, morph: false, effect: nil, **row_kwargs)
|
|
52
|
+
if model.is_a?(Phlex::Reactive::Streamable)
|
|
53
|
+
@streams << model.to_stream_replace(morph:, effect:)
|
|
54
|
+
# A built component carries its own identity, so peers can be handed
|
|
55
|
+
# the same instance — no definition needed.
|
|
56
|
+
@peer_ops << [nil, :replace, model, {}] if @handle.peers
|
|
57
|
+
return self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
definition = definition!(nil)
|
|
61
|
+
@streams.concat(Phlex::Reactive::Collections.replace_streams(definition, model, effect:, **row_kwargs))
|
|
62
|
+
# A replace is NOT a collection delta (it moves no boundary), but peers
|
|
63
|
+
# still need it: without this, a failed re-execution goes back to
|
|
64
|
+
# actionable for the actor while every other operator keeps the stale
|
|
65
|
+
# row. It rides the ordinary row broadcast, not broadcast_collection_to.
|
|
66
|
+
peer(definition, :replace, model, row_kwargs)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Remove a row: the row + the count companion + the empty-state restore at
|
|
71
|
+
# the 1->0 boundary. `from:` defaults to the collection reply.pending
|
|
72
|
+
# named, so the common case reads `s.remove(transfer)`.
|
|
73
|
+
def remove(model, from: nil, effect: nil)
|
|
74
|
+
definition = definition!(from)
|
|
75
|
+
@streams.concat(Phlex::Reactive::Collections.remove_streams(definition, @container, model, effect:))
|
|
76
|
+
peer(definition, :remove, model, {})
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Add a row: the row + the count companion + the empty-state clear at the
|
|
81
|
+
# 0->1 boundary.
|
|
82
|
+
def append(model, to: nil, effect: nil, **row_kwargs)
|
|
83
|
+
add(:append, model, to, effect, row_kwargs)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def prepend(model, to: nil, effect: nil, **row_kwargs)
|
|
87
|
+
add(:prepend, model, to, effect, row_kwargs)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The case that motivated the whole issue: the work moved a record between
|
|
91
|
+
# two lists. Ordered remove-then-append so the size resolvers run against
|
|
92
|
+
# the post-move world in the order a reader expects, and so a row can
|
|
93
|
+
# never be momentarily present in both containers.
|
|
94
|
+
def move(model, from:, to:, effect: nil, **row_kwargs)
|
|
95
|
+
remove(model, from:, effect:)
|
|
96
|
+
append(model, to:, effect:, **row_kwargs)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Refresh ONLY the collection's count companion — for a settle that
|
|
100
|
+
# changed the size without adding or removing a visible row. `name`
|
|
101
|
+
# defaults to the collection reply.pending named.
|
|
102
|
+
def count(name = nil)
|
|
103
|
+
@streams.concat(Phlex::Reactive::Collections.count_streams(definition!(name), @container))
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Tell the operator what happened. The job is where the OUTCOME is known,
|
|
108
|
+
# so this is how the page stops saying "Queued" forever.
|
|
109
|
+
def flash(level, content, target: Phlex::Reactive.flash_target, dismiss_after: nil)
|
|
110
|
+
@streams << Phlex::Reactive::Response.send(:flash_stream, level, content, target:, dismiss_after:)
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Server-pushed client DOM ops, same vocabulary as reply.js. Defaults to
|
|
115
|
+
# the container's id so self-scoped ops just work.
|
|
116
|
+
def js(ops, target: :__default)
|
|
117
|
+
resolved = target == :__default ? @handle.anchor : target
|
|
118
|
+
@streams << Phlex::Reactive::Response.js_stream(ops, target: resolved)
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Escape hatch: raw <turbo-stream> strings, exactly like reply.streams.
|
|
123
|
+
def streams!(*more)
|
|
124
|
+
@streams.concat(more.flatten)
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The peer deltas recorded alongside the actor's streams (issue #248).
|
|
129
|
+
# Collected rather than broadcast inline so Settles can send them AFTER
|
|
130
|
+
# the actor's message — the actor paid for the click and should not wait
|
|
131
|
+
# behind a fan-out of channel calls.
|
|
132
|
+
attr_reader :peer_ops
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def add(action, model, name, effect, row_kwargs)
|
|
137
|
+
definition = definition!(name)
|
|
138
|
+
@streams.concat(
|
|
139
|
+
Phlex::Reactive::Collections.add_streams(definition, @container, model, action, row_kwargs, effect:)
|
|
140
|
+
)
|
|
141
|
+
peer(definition, action, model, row_kwargs)
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Resolve the collection: the explicit keyword, else the one reply.pending
|
|
146
|
+
# named. A settle with neither is a call-site mistake — fail loudly rather
|
|
147
|
+
# than silently emit nothing.
|
|
148
|
+
def definition!(name)
|
|
149
|
+
resolved = name || @handle.collection
|
|
150
|
+
unless resolved
|
|
151
|
+
raise Phlex::Reactive::Error,
|
|
152
|
+
"this settle has no collection to work on — name it (s.remove(record, from: :items)) " \
|
|
153
|
+
"or pass in: to reply.pending so the settle inherits it"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
Phlex::Reactive::Collections.definition!(@container, resolved)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Record a peer delta (only when reply.pending asked for peers:). The row
|
|
160
|
+
# kwargs ride along: a peer whose row component has a required init kwarg
|
|
161
|
+
# would otherwise raise, and one with an optional kwarg would render
|
|
162
|
+
# different markup than the actor got.
|
|
163
|
+
def peer(definition, action, model, row_kwargs)
|
|
164
|
+
return unless @handle.peers
|
|
165
|
+
|
|
166
|
+
@peer_ops << [definition.name, action, model, row_kwargs]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|