live_cable 0.3.0 → 0.4.0
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 +567 -0
- data/README.md +14 -2
- data/app/assets/javascript/controllers/live_controller.js +51 -7
- data/app/assets/javascript/live_cable.js +2 -0
- data/app/assets/javascript/loading.js +58 -6
- data/app/assets/javascript/subscriptions.js +292 -22
- data/app/channels/live_channel.rb +51 -29
- data/app/helpers/live_cable_helper.rb +31 -2
- data/lib/live_cable/action_cable_connection.rb +4 -1
- data/lib/live_cable/component/broadcasting.rb +37 -6
- data/lib/live_cable/component/dispatching.rb +36 -0
- data/lib/live_cable/component/lifecycle.rb +1 -1
- data/lib/live_cable/component/reactive_variables.rb +33 -5
- data/lib/live_cable/component/rendering.rb +30 -14
- data/lib/live_cable/component/streaming.rb +35 -4
- data/lib/live_cable/component.rb +4 -3
- data/lib/live_cable/connection/broadcasting.rb +15 -3
- data/lib/live_cable/connection/component_management.rb +12 -8
- data/lib/live_cable/connection/error_handling.rb +28 -7
- data/lib/live_cable/connection/messaging.rb +40 -52
- data/lib/live_cable/connection/state_management.rb +13 -16
- data/lib/live_cable/connection.rb +33 -0
- data/lib/live_cable/container.rb +17 -11
- data/lib/live_cable/defaults_signer.rb +95 -0
- data/lib/live_cable/delegation/array.rb +27 -1
- data/lib/live_cable/delegation/hash.rb +55 -1
- data/lib/live_cable/delegation/methods.rb +25 -1
- data/lib/live_cable/delegation/model.rb +12 -0
- data/lib/live_cable/delegator.rb +54 -6
- data/lib/live_cable/dispatch.rb +54 -0
- data/lib/live_cable/engine.rb +3 -1
- data/lib/live_cable/forbidden.rb +7 -0
- data/lib/live_cable/identity_digest.rb +27 -0
- data/lib/live_cable/model_observer.rb +37 -0
- data/lib/live_cable/observer_tracking.rb +10 -1
- data/lib/live_cable/predicate_builder.rb +13 -0
- data/lib/live_cable/render_context.rb +82 -8
- data/lib/live_cable/rendering/controller_renderer.rb +26 -0
- data/lib/live_cable/rendering/dependency_visitor.rb +68 -24
- data/lib/live_cable/rendering/handler.rb +1 -1
- data/lib/live_cable/rendering/method_analyzer.rb +18 -4
- data/lib/live_cable/rendering/method_collector.rb +13 -5
- data/lib/live_cable/rendering/partial.rb +13 -1
- data/lib/live_cable/rendering/partial_renderer.rb +59 -7
- data/lib/live_cable/rendering/render_result.rb +5 -0
- data/lib/live_cable/rendering/renderer.rb +18 -14
- data/lib/live_cable/rendering/view_buffer.rb +21 -0
- data/lib/live_cable/testing/render_state.rb +5 -3
- data/lib/live_cable/testing/test_channel.rb +6 -0
- data/lib/live_cable/testing/test_component.rb +3 -3
- data/lib/live_cable/testing.rb +9 -5
- data/lib/live_cable/version.rb +1 -1
- data/lib/live_cable.rb +32 -3
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e057aa97b7e7358e132b96ab748c410e60b4d60012ae12bbc8f1007e7e49465a
|
|
4
|
+
data.tar.gz: aa0da7bec89699efe1eb46ceae11a58fdd686db5f6e39d6d45b36e3304f52d57
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6327de430dba2dc315a2eb220f12f5ae4934630f0e18533aba25de624f963d68a19155eb9857282655978f363ec524ee850f57080aa738e0f308cda6946e7a9
|
|
7
|
+
data.tar.gz: 630e1deca5298f6c40b80b626b89a71c7848ddba520b4e635dfe3f7db105f6dd5469c0296789ad116d3718e518be33ad1f8d9ed11d8b5a94b7e66c6a591a34fb
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,573 @@ The Ruby gem (`live_cable`) and the npm package (`@isometriks/live_cable`) are
|
|
|
6
6
|
released together and share a single version number. Entries below note which
|
|
7
7
|
side of the pair a change affects when it isn't both.
|
|
8
8
|
|
|
9
|
+
## Unreleased
|
|
10
|
+
|
|
11
|
+
### Upgrading from 0.4
|
|
12
|
+
|
|
13
|
+
The gem and the npm package work with each other at 0.4 in either
|
|
14
|
+
direction, but upgrade both to get every fix. A few fixes change behaviour an
|
|
15
|
+
application could notice:
|
|
16
|
+
|
|
17
|
+
- A template that reads a local assigned inside an earlier block, with no
|
|
18
|
+
component method or view helper of that name, now raises `NoMethodError`
|
|
19
|
+
instead of rendering nothing.
|
|
20
|
+
- A component method named like a Kernel method (`open`, `test`, `select`, …)
|
|
21
|
+
now wins over Kernel in its template; call it on `Kernel`, as in
|
|
22
|
+
`Kernel.rand`, to reach Kernel's. Don't name a component method `format`:
|
|
23
|
+
ActionView reads it as the template's format.
|
|
24
|
+
- A tag that reassigns a local set by an earlier top-level `<% %>` tag now
|
|
25
|
+
re-runs on every update, so later tags see the right value. Keep expensive
|
|
26
|
+
work out of such tags.
|
|
27
|
+
- A loading state now ends only on the component's own reply. A message held
|
|
28
|
+
while the socket was down stays loading until it is answered.
|
|
29
|
+
- A focused text field keeps what the user typed through renders it didn't
|
|
30
|
+
ask for. To clear a field after a submit, bind it to a reactive variable and
|
|
31
|
+
reset that in the action.
|
|
32
|
+
- `live_mount(..., raise_errors: false)` no longer raises when the first
|
|
33
|
+
render fails; assert on `broadcasts(:_error)` instead.
|
|
34
|
+
- A template now gets the plain Array, Hash or model inside a reactive
|
|
35
|
+
variable, so Rails helpers and `case` see the real value. A template that
|
|
36
|
+
changes a reactive value in place no longer marks it dirty; change it in the
|
|
37
|
+
component. `component.x` in a template still returns the tracked wrapper.
|
|
38
|
+
- In component code, values reached through `fetch`, `dig`, `detect`,
|
|
39
|
+
`Hash#each` and `each_value` are tracked wrappers now, so writes through
|
|
40
|
+
them mark the variable dirty. Use `to_a`, `to_h` or `__getobj__` where code
|
|
41
|
+
needs the real Array or Hash.
|
|
42
|
+
- A model held in a reactive variable is watched on the record itself. A
|
|
43
|
+
write through any reference to it, a `reload`, or a validation that
|
|
44
|
+
normalises an attribute now marks the variable dirty and re-renders.
|
|
45
|
+
- An error raised by an initial-value lambda goes through normal error
|
|
46
|
+
handling instead of storing `nil`: a root's subscribe answers with an
|
|
47
|
+
`_error`, and a failing child replaces its parent with the error box. An
|
|
48
|
+
initial value that isn't a proc raises `ArgumentError` when the class loads.
|
|
49
|
+
- Defaults are signed in their `as_json` form, and a prerendered root
|
|
50
|
+
renders with that form too, so a Symbol default is a String on page load as
|
|
51
|
+
well as after subscribe. In development and test, a default that changes on
|
|
52
|
+
the way logs a warning.
|
|
53
|
+
- A child its parent renders now runs its render callbacks on each of those
|
|
54
|
+
renders. On page load they run before its connect callbacks, without
|
|
55
|
+
connection identifiers such as `current_user`.
|
|
56
|
+
- When a component re-renders, parts that read a non-reactive `shared`
|
|
57
|
+
variable render again too.
|
|
58
|
+
- A checkbox with `live-reactive` sends `true` or `false`, and a `<select
|
|
59
|
+
multiple>` sends an Array of values, instead of the element's `value`.
|
|
60
|
+
- A render that changes nothing on the page is no longer sent. An action
|
|
61
|
+
whose changes no part of the template shows is answered with an `_ack`, so
|
|
62
|
+
a test expecting a `_refresh` after one should expect `broadcasts(:_ack)`.
|
|
63
|
+
- Renders over the socket now share one controller per connection. A
|
|
64
|
+
`helper_method` on `ApplicationController` that memoizes into an instance
|
|
65
|
+
variable keeps its value for the connection rather than for one render.
|
|
66
|
+
These renders have no session or params, so this only matters for a value
|
|
67
|
+
that changes over time.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **A template local could render blank or stale.** Dependency
|
|
72
|
+
tracking parsed each part of a `.live.erb` template on its own and ignored
|
|
73
|
+
Ruby's block scopes. A local assigned inside a block leaked into later
|
|
74
|
+
parts, so a later `<%= css %>` rendered blank instead of calling the
|
|
75
|
+
component's `css`; a top-level local updated inside a block, as in
|
|
76
|
+
`<% counts.each { |c| total += c } %>`, kept its earlier value in later
|
|
77
|
+
parts; and a local reassigned in an `if` that didn't run became `nil`.
|
|
78
|
+
Block parameters also counted as dependencies, so a part re-rendered
|
|
79
|
+
whenever a reactive variable of the same name changed. Each part is now
|
|
80
|
+
parsed with the earlier parts' locals in scope, so names resolve as they
|
|
81
|
+
would in a plain ERB template. Two templates behave differently: one that
|
|
82
|
+
reads a block's local after the block, with no component method or view
|
|
83
|
+
helper of that name, now raises `NoMethodError` ("undefined method 'last'"
|
|
84
|
+
for a local named `last`) instead of rendering nothing, much as plain ERB
|
|
85
|
+
raises `NameError`; and a local reassigned in a branch that doesn't run
|
|
86
|
+
keeps its value instead of becoming `nil` (gem).
|
|
87
|
+
- **A block passed to a view helper that renders a partial landed above the
|
|
88
|
+
partial.** A helper such as `def card(&) = render(layout: 'shared/card', &)`,
|
|
89
|
+
used from a `.live.erb` template as `<%= card do %>`, wrote the block's
|
|
90
|
+
content before the card and left stray escaped markup where the card
|
|
91
|
+
yields. 0.4.0 fixed this for `render` called by the template itself; a
|
|
92
|
+
block the template passes to any view helper is now handled the same way.
|
|
93
|
+
Helpers that capture their block, such as `form_with` and `content_tag`,
|
|
94
|
+
are unaffected (gem).
|
|
95
|
+
- **The error for a `.live.erb` template rendered as a partial didn't say
|
|
96
|
+
which template.** The message began "A .live.erb template", and the
|
|
97
|
+
backtrace points into LiveCable rather than at the template. It now opens
|
|
98
|
+
with the template's path, such as
|
|
99
|
+
`app/views/shared/_live_card.html.live.erb` (gem).
|
|
100
|
+
- **Template names that Kernel or Object also define, like `open`, `select`
|
|
101
|
+
or `test`, ran the Kernel method instead of the component's or the view
|
|
102
|
+
helper's.** A `.live.erb` template resolves bare names to the component and
|
|
103
|
+
then the view context, but names the renderer already had from Kernel or
|
|
104
|
+
Object never got that far. A component with `reactive :open` and
|
|
105
|
+
`<% if open %>` raised an `ArgumentError` on the page and on mount, the
|
|
106
|
+
`select` form helper raised a `TypeError`, and `j` printed to standard output
|
|
107
|
+
instead of escaping. These names now go to the component or the view context
|
|
108
|
+
whenever either defines them. When neither does they still call the Kernel
|
|
109
|
+
method, so `format`, `rand` and `lambda` work as before. A component method
|
|
110
|
+
with such a name now wins in its template; call it on `Kernel`, as in
|
|
111
|
+
`Kernel.rand`, to reach the Kernel method (gem).
|
|
112
|
+
- **A child hidden after its parent re-rendered something else was never
|
|
113
|
+
destroyed.** A parent only remembered the inline children of the parts its
|
|
114
|
+
last render had run, so after a render that skipped a child's part it no
|
|
115
|
+
longer knew it had that child. That happened on any change the part doesn't
|
|
116
|
+
depend on, and whenever the parent was re-rendered inline by its own parent
|
|
117
|
+
with nothing changed. Hiding the child later, a render error in the parent,
|
|
118
|
+
or the parent's own destroy then left the child subscribed, with its
|
|
119
|
+
`stream_from` streams running, until the socket closed. A compound component
|
|
120
|
+
that switched to a variant with fewer parts leaked the children of the parts
|
|
121
|
+
that went away in the same way. A parent now keeps the children of the parts
|
|
122
|
+
a render skipped, and destroys any its new template no longer renders (gem).
|
|
123
|
+
- **A `stream_from` callback could still run after `stop_stream`.**
|
|
124
|
+
ActionCable runs each broadcast as its own job on its worker pool, and
|
|
125
|
+
stopping a stream can't take back a job already queued. A chat that
|
|
126
|
+
switched rooms with `stop_stream` and a new `stream_from` could still run a
|
|
127
|
+
callback for the old room after the switch. With a reactive array, the old
|
|
128
|
+
room's message was appended to the new room's list and stayed there. A
|
|
129
|
+
callback now checks, under the connection lock, that its stream is still
|
|
130
|
+
running, so once `stop_stream` returns none of its callbacks run. Stopping a
|
|
131
|
+
stream and starting it again under the same name still delivers what was
|
|
132
|
+
already queued. `stop_stream` also kept every name it had stopped, a list
|
|
133
|
+
that grew with each switch. The new `stop_stream_from(name)` stops one
|
|
134
|
+
stream and leaves the component's others running (gem).
|
|
135
|
+
- **Another user's broadcast re-enabled a `live-disable-with` button whose own
|
|
136
|
+
message was still in flight.** The client ended a component's loading state
|
|
137
|
+
on every `_refresh`, including renders it never asked for - a `stream_from`
|
|
138
|
+
callback's, or one caused by another component's action - and a parent's
|
|
139
|
+
re-render rebuilt a nested component's pending button, so someone else's
|
|
140
|
+
chat message could re-enable your Send button and invite a double submit.
|
|
141
|
+
A nested component re-rendered only inside its parent's render got no reply
|
|
142
|
+
at all, and stalled after 30 seconds. The server now
|
|
143
|
+
marks the render that answers a message with `_reply`, or sends an `_ack`
|
|
144
|
+
when the component sent no render of its own, and the client ends a loading
|
|
145
|
+
state only on a reply, an `_ack` or an `_error`; pending elements keep their
|
|
146
|
+
state through other renders and catch up when it comes. Frames captured by
|
|
147
|
+
`LiveCable::Testing` carry the new `_reply` and `_subscribed` keys. A message
|
|
148
|
+
held while the socket was down now stays loading until its own reply, and
|
|
149
|
+
one lost with the socket stops when it closes or the component
|
|
150
|
+
re-subscribes. Either side works with the other at 0.4.0 (gem + npm).
|
|
151
|
+
- **Typing in a focused field was undone by a render the user didn't ask
|
|
152
|
+
for.** Every refresh is morphed in from HTML rebuilt from the component's
|
|
153
|
+
stored parts, which set each field back to the last value the server
|
|
154
|
+
rendered. In a chat with a debounced `live-reactive` draft, each incoming
|
|
155
|
+
`stream_from` message, or a render caused by another component, dropped
|
|
156
|
+
what had been typed since the last send, and the debounced send that
|
|
157
|
+
followed sent what was left. A focused text field or textarea now keeps
|
|
158
|
+
what has been typed unless the server changed its value. A re-render that
|
|
159
|
+
answers the field's own form or action still applies the server's value,
|
|
160
|
+
so an input cleared after a submit is still cleared. Unfocused fields,
|
|
161
|
+
checkboxes, radios and selects behave as before. A focused field that
|
|
162
|
+
isn't bound to a reactive variable is no longer cleared by a render it
|
|
163
|
+
didn't ask for; to clear it after a submit, bind it and reset the variable
|
|
164
|
+
in the action (npm).
|
|
165
|
+
- **A Turbo Drive visit removed the inline children of a component that
|
|
166
|
+
stayed on the page.** Before Turbo renders a new page, LiveCable closes the
|
|
167
|
+
subscription of every component that isn't on it. The HTTP prerender gives
|
|
168
|
+
a live id only to the root component, so a child rendered inline by its
|
|
169
|
+
parent looked like it was leaving, even when the parent (a layout
|
|
170
|
+
component, say) was staying. The child was torn down on the server, and the
|
|
171
|
+
parent had nothing to rebuild it from, so it disappeared from the page. A
|
|
172
|
+
component that stays on the page now also keeps the children it renders
|
|
173
|
+
with `.live.erb` templates, and theirs, with their subscriptions and live
|
|
174
|
+
state (npm).
|
|
175
|
+
- **`live_mount(..., raise_errors: false)` still raised when the first render
|
|
176
|
+
or a `connect` callback failed.**
|
|
177
|
+
`live_mount` ran `connect` and the first render with no rescue, so an error
|
|
178
|
+
in either still raised, while production sends it through the connection's
|
|
179
|
+
error handling and transmits an `_error`. `live_mount` now handles those
|
|
180
|
+
errors the same way `LiveChannel#subscribed` does. With `raise_errors: false`
|
|
181
|
+
it returns the component with the `_error` in its broadcasts, so a spec that
|
|
182
|
+
expected `live_mount(..., raise_errors: false)` to raise now needs to assert
|
|
183
|
+
on `broadcasts(:_error)` instead. The default, `raise_errors: true`, still
|
|
184
|
+
raises (gem).
|
|
185
|
+
- **`component.x` and `self.x` in a template didn't re-render when `x`
|
|
186
|
+
changed.** Dependency tracking only recorded bare names, so a part that read
|
|
187
|
+
a reactive variable as `component.theme` or `self.theme` was skipped when
|
|
188
|
+
`theme` changed and kept showing the old value. `self.x` also ran Kernel's
|
|
189
|
+
method of that name when there was one, so `self.open` raised. Both forms
|
|
190
|
+
now count as reads of the component (gem).
|
|
191
|
+
- **A helper called with arguments rendered a template local of the same
|
|
192
|
+
name instead.** After `<% t = Time.current %>`, a later `<%= t('.title') %>`
|
|
193
|
+
printed the time: a call with arguments still found the stored local before
|
|
194
|
+
the helper. A local now only answers a bare name, as in plain ERB (gem).
|
|
195
|
+
- **`items.each.with_index` raised `LocalJumpError` on reactive arrays.** A
|
|
196
|
+
reactive array's `each` always yielded, so calling `each` without a block
|
|
197
|
+
failed, and so did `each.with_index`, `each.with_object` and `each.lazy`. In
|
|
198
|
+
a template this meant the prerender worked, because it sees the raw array,
|
|
199
|
+
but every socket render failed and the component never went live. `each`
|
|
200
|
+
without a block now returns an enumerator, and elements yielded through it
|
|
201
|
+
are tracked like those from `each` with a block (gem).
|
|
202
|
+
- **Some common ways of changing a reactive Array or Hash didn't re-render.**
|
|
203
|
+
Change tracking only knew the method names it listed, so aliases and
|
|
204
|
+
ActiveSupport bang methods went straight through untracked. That covered
|
|
205
|
+
`append`, `prepend`, `filter!`, `collect!`, `compact_blank!` and `extract!`,
|
|
206
|
+
and on hashes `store`, `replace`, `slice!`, `with_defaults!`,
|
|
207
|
+
`deep_symbolize_keys!` and others. Nested values read through `detect`,
|
|
208
|
+
`fetch`, `dig`, `at`, `min_by`, `each_with_index`, `each_with_object`,
|
|
209
|
+
`reverse_each`, `each_slice`, `values`, `each_value` or `Hash#each` came
|
|
210
|
+
back untracked. So `todos.detect { ... }[:done] = true` answered with an
|
|
211
|
+
`_ack` and left the page stale. All of these are now tracked, and a spec
|
|
212
|
+
fails if a mutating method is missed. Values yielded by these methods are
|
|
213
|
+
now tracked wrappers, like the ones `each` already yielded, so `is_a?(Hash)`
|
|
214
|
+
on them is false. The blocks of `map`/`select` and the results of
|
|
215
|
+
`to_a`/`to_h` are still untracked; see *Nested Structures* in the reactive
|
|
216
|
+
variables guide (gem).
|
|
217
|
+
- **`self.tags = tags.dup` stopped all later change tracking for `tags`.**
|
|
218
|
+
`dup` dropped the wrapper's tracking modules, so the assignment rendered
|
|
219
|
+
once and every later `tags << x` or nested change went unnoticed. A
|
|
220
|
+
duplicated reactive value now keeps its tracking. `clone(freeze: false)` on
|
|
221
|
+
a reactive value or an ActiveRecord model raised `ArgumentError` and now
|
|
222
|
+
works (gem).
|
|
223
|
+
- **Changing an element moved into another reactive collection marked the
|
|
224
|
+
wrong variable.** After `done << todos.find { ... }` (or `self.done =
|
|
225
|
+
[item]`, `done + [item]`, `selection[:item] = item`), changing the element
|
|
226
|
+
through `done` marked `todos` instead. That included a child component given
|
|
227
|
+
the element with `live(...)`, so the `done` part never re-rendered. Reactive
|
|
228
|
+
collections now store the plain element rather than the tracked wrapper it
|
|
229
|
+
was read through, and an element read back is tracked through the collection
|
|
230
|
+
it was read from. An Array or Hash you pass in is copied only when it holds
|
|
231
|
+
such a wrapper (gem).
|
|
232
|
+
- **ActiveRecord models read from a reactive variable never compared equal.**
|
|
233
|
+
Two reads of the same record, or a raw record and a wrapped one, compared
|
|
234
|
+
unequal. So `todos.delete(todos.find { ... })` left the record in the list,
|
|
235
|
+
`include?`, `index` and `todos - [found]` missed it, and `t == selected` in
|
|
236
|
+
a template never matched. Equality now compares the records themselves.
|
|
237
|
+
Unsaved records still compare unequal, as ActiveRecord intends (gem).
|
|
238
|
+
- **Many changes to ActiveRecord models in reactive variables didn't
|
|
239
|
+
re-render.** A model reached through a reactive Array or Hash
|
|
240
|
+
(`todos.first.title = x`, `todos.find { ... }.toggle!(:done)`, setters
|
|
241
|
+
inside `todos.each`) was only tracked for `update` and `assign_attributes`.
|
|
242
|
+
Even a model stored directly missed `[]=`, `write_attribute`, `increment!`,
|
|
243
|
+
`update_column(s)`, `reload` and saved in-place JSON changes, so the
|
|
244
|
+
streaming guide's `document.reload` example never refreshed the page. All of
|
|
245
|
+
these writes are now tracked, at any depth. A replaced record no longer
|
|
246
|
+
keeps marking the variable it was removed from. `reload`, and validations
|
|
247
|
+
that normalize attributes, now re-render even when nothing visible changed.
|
|
248
|
+
Association changes and unsaved in-place edits still need `dirty(:name)`
|
|
249
|
+
(gem).
|
|
250
|
+
- **After connecting, reactive Arrays and Hashes rendered wrongly in Rails tag
|
|
251
|
+
helpers, and `where(id: reactive_array)` matched nothing.** A tracked value
|
|
252
|
+
isn't an `Array` or `Hash` to `case` or `===`. On socket renders,
|
|
253
|
+
`tag.span(class: classes)` printed `class="["btn",
|
|
254
|
+
"primary"]"` where the prerender printed `class="btn primary"`,
|
|
255
|
+
and `where(id: ids)` compiled to `id = NULL`. Templates now see the plain
|
|
256
|
+
value, so helpers and `case` behave as in any Rails view, and `where(column:
|
|
257
|
+
value)` accepts reactive values. A child given one of these with `live(...)`
|
|
258
|
+
still shares its change tracking, so the parent re-renders when the child
|
|
259
|
+
changes it. That covers a reactive value, an element of one, and a
|
|
260
|
+
collection built from one in the template, such as `todos.reject { ... }`. A
|
|
261
|
+
template that mutates a reactive value no longer marks it dirty. A
|
|
262
|
+
variable's first read now returns the tracked value like later reads, so
|
|
263
|
+
changes made through it are tracked. In component code, `case`,
|
|
264
|
+
`where(hash)` and association assignment still need `to_a`, `to_h` or
|
|
265
|
+
`__getobj__` (gem).
|
|
266
|
+
- **A part that passed a reactive variable with hash shorthand (`render
|
|
267
|
+
'badge', page:`) or called it with empty parentheses (`count()`) never
|
|
268
|
+
re-rendered.** The dependency analysis only recognised a bare `page`, so
|
|
269
|
+
these forms (`tag.span(data: { page: })`, `items_path(page:)`, a shorthand
|
|
270
|
+
of a template local set in an earlier part) were left out and the part kept
|
|
271
|
+
its first-render HTML. Any receiverless call with no arguments and no block
|
|
272
|
+
now counts as a read. (gem)
|
|
273
|
+
- **A component method's dependencies were lost when another def in the same
|
|
274
|
+
file had the same name.** The method analyzer parses the component's whole
|
|
275
|
+
file and the last def with a given name won, so a `def self.title`, a `class
|
|
276
|
+
<< self` method, a method in a nested class or `Struct.new` block, or a
|
|
277
|
+
method of a second class in the file could leave every part calling `title`
|
|
278
|
+
stale. Same-named defs now merge their dependencies, and singleton methods
|
|
279
|
+
are ignored. Affected parts may now re-render where they were previously
|
|
280
|
+
skipped. (gem)
|
|
281
|
+
- **Parts reading a non-reactive `shared` variable in a `.live.erb` template
|
|
282
|
+
never re-rendered.** They kept their first-render value even when the
|
|
283
|
+
component re-rendered for its own reasons or wrote the variable itself, so
|
|
284
|
+
the guide's FilterPanel badge example showed a stale count. Whenever a
|
|
285
|
+
component re-renders, parts that read its `shared` variables, directly or
|
|
286
|
+
through a component method, now re-render too. A change to a `shared`
|
|
287
|
+
variable on its own still does not trigger a render. These parts are now
|
|
288
|
+
sent on every re-render of the component. (gem)
|
|
289
|
+
- **The guide's `reactive :user, ->(c) { User.find(c.defaults[:user_id]) }`
|
|
290
|
+
raised `NoMethodError`.** `Component#defaults` was private, and an initial
|
|
291
|
+
lambda gets the component as an explicit receiver. So the prerender failed,
|
|
292
|
+
`live_mount` raised, and a real subscribe sent an `_error` and rendered
|
|
293
|
+
without the default. `defaults` is now public. As a side effect, a bare
|
|
294
|
+
`defaults` in a `.live.erb` template now resolves to the component's hash
|
|
295
|
+
(gem).
|
|
296
|
+
- **A top-level component could render one way on page load and another once
|
|
297
|
+
connected.** Its defaults come back from the browser as JSON on subscribe,
|
|
298
|
+
but the prerender used the raw Ruby values. A Symbol came back as a String
|
|
299
|
+
and nested hash keys as Strings. Since signing in 0.4.0 used plain
|
|
300
|
+
`JSON.generate`, a `Time` came back as its `to_s` and a record as
|
|
301
|
+
`"#<User:0x…>"`. LiveCable now signs each default's `as_json` form, as 0.3's
|
|
302
|
+
unsigned attribute did, and prerenders a top-level component with that same
|
|
303
|
+
form, so both renders agree. In development and test it logs a warning for
|
|
304
|
+
any default that isn't JSON-native. **Never pass a record to a top-level
|
|
305
|
+
component: its JSON form is every one of its attributes, readable in the
|
|
306
|
+
page.** The defaults are signed, not encrypted. Pass an id and load the
|
|
307
|
+
record in the component. A top-level component now gets the JSON form on the
|
|
308
|
+
HTTP prerender too. So `status: :open` is now `"open"` there, and
|
|
309
|
+
`user.name` on a passed record now raises on page load (a 500 in production)
|
|
310
|
+
instead of after connect. Children rendered by a parent and `live_mount`
|
|
311
|
+
defaults are unchanged (gem).
|
|
312
|
+
- **An exception in a reactive variable's initial lambda was swallowed and the
|
|
313
|
+
variable stuck at `nil` (or `true`).** On a connected component the error
|
|
314
|
+
was reported, and then `handle_error`'s return value was stored as the
|
|
315
|
+
variable. That was `nil` for a child rendered inline and `true` for a
|
|
316
|
+
component with a channel, and the lambda never ran again. A child whose
|
|
317
|
+
lambda failed rendered blank, with nothing sent to the client, and a failed
|
|
318
|
+
shared variable stayed broken for every later sharer. The error now goes
|
|
319
|
+
through the normal handling for the render or action that read it, including
|
|
320
|
+
`rescue_from`, and nothing is stored, so the next read retries. A top-level
|
|
321
|
+
component's subscribe now sends just the `_error` (no trailing `_refresh`).
|
|
322
|
+
A child's failing initial value now replaces its parent with the error box.
|
|
323
|
+
A non-proc initial value now raises `ArgumentError` when the class loads
|
|
324
|
+
(gem).
|
|
325
|
+
- **Initial lambdas ran again on every read during the HTTP prerender.** With
|
|
326
|
+
no connection, the reactive getter never kept the value, so a `.live.erb`
|
|
327
|
+
template ran the lambda once per reference, plus once for the render locals.
|
|
328
|
+
A query read three times ran four times, and `-> { SecureRandom.hex }` gave
|
|
329
|
+
a label's `for` and its input's `id` different values on the same page. The
|
|
330
|
+
value is now kept for the rest of that prerender (gem).
|
|
331
|
+
- **`before_render` and `after_render` never ran for a child rendered by its
|
|
332
|
+
parent.** Render callbacks ran only when a component broadcast its own
|
|
333
|
+
render. A child rendered inside a connected parent was never rendered that
|
|
334
|
+
way: on page load its subscription found it already rendered and only
|
|
335
|
+
confirmed its status. So state a child derived in `before_render` showed its
|
|
336
|
+
initial value and went stale every time the parent rendered it again. A
|
|
337
|
+
child's render callbacks now run each time a connected parent renders it,
|
|
338
|
+
and an error in one replaces the parent with the error box. On page load the
|
|
339
|
+
parent renders the child before the child subscribes, so the child's render
|
|
340
|
+
callbacks run before its connect callbacks, without connection identifiers
|
|
341
|
+
such as `current_user`. A child whose `before_render` uses one will now
|
|
342
|
+
error its parent on page load. Render callbacks still don't run during the
|
|
343
|
+
HTTP prerender, which the lifecycle guide now says (gem).
|
|
344
|
+
- **A checkbox with `live-reactive` could never be unchecked, and a `<select
|
|
345
|
+
multiple>` kept only its first choice.** `live-reactive` always sent the
|
|
346
|
+
input's `value`. For a checkbox that is its `value` attribute ("on" by
|
|
347
|
+
default), whether the box is checked or not. For a multiple select it is
|
|
348
|
+
only the first selected option. A checkbox now sends `true` or `false`, and
|
|
349
|
+
a `<select multiple>` sends an Array of the selected values. Radios, single
|
|
350
|
+
selects and text inputs still send their value as a String. If you were
|
|
351
|
+
reading "on" (or a custom `value` attribute) from a checkbox, you now get a
|
|
352
|
+
boolean (npm).
|
|
353
|
+
|
|
354
|
+
### Changed
|
|
355
|
+
|
|
356
|
+
- **A render that changed nothing on the page was still sent.** When a
|
|
357
|
+
reactive variable changed but no part of the template showed it - a shared
|
|
358
|
+
variable updated by another component, say - the component still sent a
|
|
359
|
+
`_refresh` with every part empty, and the client rebuilt and morphed it for
|
|
360
|
+
no visible change. Such a render is now skipped: its events still go out,
|
|
361
|
+
its render callbacks still run, and the message it answers gets an `_ack`.
|
|
362
|
+
- **Every render over the socket built a controller, request and response.**
|
|
363
|
+
`Component#render` went through `ApplicationController.renderer`, which
|
|
364
|
+
builds all three each time - about a fifth of the render time of a small
|
|
365
|
+
component. A connection now builds them once, from the same renderer
|
|
366
|
+
defaults, and each render takes a fresh view from that controller, so
|
|
367
|
+
`content_for`, instance variables a template sets and memoized view helpers
|
|
368
|
+
still don't carry over. The HTTP prerender is unchanged (gem).
|
|
369
|
+
|
|
370
|
+
## 0.4.0 - 2026-09-29
|
|
371
|
+
|
|
372
|
+
### Upgrading from 0.3
|
|
373
|
+
|
|
374
|
+
Upgrade the gem and the npm package together. Defaults are now signed, and
|
|
375
|
+
the client forwards them as an opaque string; a 0.3 client sends them as an
|
|
376
|
+
object, which the server treats as no defaults at all, so every component
|
|
377
|
+
would mount without them.
|
|
378
|
+
|
|
379
|
+
Pages rendered before the upgrade carry unsigned defaults too. They mount with
|
|
380
|
+
no defaults until reloaded, so deploy at a quiet moment, or expect components
|
|
381
|
+
on open tabs to come up empty.
|
|
382
|
+
|
|
383
|
+
A component already live on the page is now built again when a Turbo visit
|
|
384
|
+
renders it with different defaults. One whose defaults differ on every
|
|
385
|
+
render - a timestamp, a random token - is therefore rebuilt on every visit
|
|
386
|
+
instead of keeping its state; keep defaults to what the component is about,
|
|
387
|
+
such as a record or an account.
|
|
388
|
+
|
|
389
|
+
A shared variable is now writable from the client only if every component
|
|
390
|
+
class that shares the name declares it `writable: true`. An application that
|
|
391
|
+
shares a name as writable in one component and server-only in another will
|
|
392
|
+
see `live-reactive` writes to it refused.
|
|
393
|
+
|
|
394
|
+
### Security
|
|
395
|
+
|
|
396
|
+
- **Defaults could set reactive variables that weren't writable.** Defaults
|
|
397
|
+
passed to `live(...)` are written into the page and sent back by the browser
|
|
398
|
+
when the component subscribes, and they were applied to any reactive
|
|
399
|
+
variable, writable or not. Anyone could edit the `live-defaults` attribute,
|
|
400
|
+
or send the subscribe frame by hand, and set a variable meant to be
|
|
401
|
+
server-only - a record id, a price, a tenant key - bypassing the check
|
|
402
|
+
`live-reactive` writes go through. Defaults are now signed with a key
|
|
403
|
+
derived from `secret_key_base` and bound to the component's `live_id`; an
|
|
404
|
+
edited, unsigned or borrowed blob applies no defaults
|
|
405
|
+
(`LiveCable::DefaultsSigner`). `LiveCable::Testing#live_mount` still takes a
|
|
406
|
+
plain hash.
|
|
407
|
+
- **A component could keep acting for the previous page's defaults after a
|
|
408
|
+
Turbo visit.** When the new page rendered a component with the same id as
|
|
409
|
+
one already live, the client kept the existing subscription and never sent
|
|
410
|
+
the new page's defaults. A component whose id didn't include the account
|
|
411
|
+
stayed bound to the account it was first rendered for, and its actions ran
|
|
412
|
+
against that account under a URL for another. The client now compares the
|
|
413
|
+
signed defaults and, when they differ, replaces the subscription so the
|
|
414
|
+
server builds the component from the new ones; equal defaults keep today's
|
|
415
|
+
behaviour, state included. A Turbo preview of a cached page is ignored.
|
|
416
|
+
Defaults are signed with their keys sorted, so the same defaults always
|
|
417
|
+
produce the same blob.
|
|
418
|
+
- **A client could write a shared variable that a component treats as
|
|
419
|
+
server-only.** Shared variables are one value per connection, but a
|
|
420
|
+
`live-reactive` write was checked only against the class it was sent
|
|
421
|
+
through. Because a client can subscribe any component, it could mount one
|
|
422
|
+
that shares `:account_id` as `writable: true` and set the value another
|
|
423
|
+
component shares without `writable:` (or with plain `shared`). A client may
|
|
424
|
+
now write a shared name only if every class that shares it declares it
|
|
425
|
+
`writable: true`. Other writes are refused with `Forbidden`. Apps that mix
|
|
426
|
+
writable and server-only declarations of the same shared name will see those
|
|
427
|
+
writes refused. In development with lazy loading, a class that hasn't loaded
|
|
428
|
+
yet doesn't count toward the rule (gem).
|
|
429
|
+
|
|
430
|
+
### Fixed
|
|
431
|
+
|
|
432
|
+
- **An action and a `stream_from` callback could run at the same time on one
|
|
433
|
+
connection.** ActionCable runs each message and each stream broadcast as a
|
|
434
|
+
separate job on a worker pool shared by the whole server, with nothing
|
|
435
|
+
keeping two jobs for one socket apart, and LiveCable's per-connection state
|
|
436
|
+
was read and written from both. An action that saved a record and then
|
|
437
|
+
pushed it onto a reactive array could have the save's own broadcast reload
|
|
438
|
+
the array in between, and the record appeared twice. Each connection's work
|
|
439
|
+
is now serialised by a re-entrant lock, held across an action or callback
|
|
440
|
+
and its render. A job waiting on a busy connection holds a worker while it
|
|
441
|
+
waits; see *Concurrency* in the architecture guide (gem).
|
|
442
|
+
- **A click while the socket was down left its button disabled for good.**
|
|
443
|
+
ActionCable drops a message sent on a closed socket, and one sent after a
|
|
444
|
+
reconnect but before the re-subscribe is confirmed, and the loading state
|
|
445
|
+
had already started. Messages are now held until the subscription is
|
|
446
|
+
confirmed and sent in order, and the component reads
|
|
447
|
+
`data-live-status-value="disconnected"` meanwhile (npm).
|
|
448
|
+
- **A loading state waited for ever when no reply came** - the server
|
|
449
|
+
stopping mid-message, on a deploy. After `LoadingState.timeout` (30 seconds)
|
|
450
|
+
with nothing heard it gives up, restores the DOM, drops any messages still
|
|
451
|
+
held for a reconnect, sets the status to `stalled` and dispatches a
|
|
452
|
+
`live:stalled` event. Messages still held when a component's element is
|
|
453
|
+
replaced, by a Turbo navigation say, keep their loading state on the new
|
|
454
|
+
element (npm).
|
|
455
|
+
- **Events dispatched by a child rendered inline by its parent were dropped.**
|
|
456
|
+
Such a child has no channel of its own yet; its events were flushed and then
|
|
457
|
+
discarded. They now wait for the child's own subscription (gem).
|
|
458
|
+
- **Component names that collide with a top-level constant failed obscurely.**
|
|
459
|
+
`instance_from_string` used `const_defined?`, which inherits, so a name like
|
|
460
|
+
`"string"` slipped past the "not found" guard and raised `NoMethodError`
|
|
461
|
+
instead of `LiveCable::Error` (gem).
|
|
462
|
+
- **A `stream_from` callback re-sent whatever the last message had changed.**
|
|
463
|
+
Changesets were reset only when a client message arrived. Variables dirtied
|
|
464
|
+
by the last message, or by the defaults applied at subscribe, stayed dirty,
|
|
465
|
+
and every later stream callback on the socket re-rendered them, whichever
|
|
466
|
+
component of the connection they belonged to. In a chat whose input is a
|
|
467
|
+
component of its own, as in the streaming guide, each incoming message
|
|
468
|
+
re-rendered the input with its last-sent `live-reactive` value, and the
|
|
469
|
+
morph overwrote whatever had been typed since the last debounce. A stream
|
|
470
|
+
callback now resets the changesets before it runs, as a message does, so it
|
|
471
|
+
sends only what it changed (gem).
|
|
472
|
+
- **Grandchild components went missing from the page.** A refresh replaced
|
|
473
|
+
the `<LiveCable>` placeholders in its own markup but not the ones inside a
|
|
474
|
+
child it had just built or rebuilt, so a grandchild reached the morph as an
|
|
475
|
+
empty element: a tree nested three deep lost its third level as soon as the
|
|
476
|
+
page connected, and a grandchild added by its parent's own refresh was torn
|
|
477
|
+
out the next time its grandparent re-rendered. Placeholders are now
|
|
478
|
+
replaced until none remain. A component that turns up twice in one refresh
|
|
479
|
+
keeps its first appearance and logs a console error, and a child with
|
|
480
|
+
nothing to rebuild from is left empty instead of abandoning the refresh
|
|
481
|
+
(npm).
|
|
482
|
+
- **Rendering a partial with a block from a `.live.erb` template failed.**
|
|
483
|
+
The block was dropped on its way to the view, so
|
|
484
|
+
`<%= render layout: 'shared/card' do %>` raised `ArgumentError`, and
|
|
485
|
+
`<%= render 'shared/card' do %>` rendered the partial with nothing where it
|
|
486
|
+
yields. The block is now passed on and renders where the partial yields (gem).
|
|
487
|
+
- **Rendering a `.live.erb` template as a partial put
|
|
488
|
+
`#<LiveCable::Rendering::Partial:0x...>` in the page.** A `.live.erb`
|
|
489
|
+
template compiles to an object only a component knows how to render, so
|
|
490
|
+
`<%= render 'shared/card' %>` on a `_card.html.live.erb`, or
|
|
491
|
+
`render template:` on one, wrote that object's escaped name into the HTML.
|
|
492
|
+
It now raises `LiveCable::Error`, saying to render the component with
|
|
493
|
+
`live(...)` or `render(component)`, or to make the partial a `.html.erb`
|
|
494
|
+
template (gem).
|
|
495
|
+
- **A `stream_from` broadcast that arrived as its component disconnected
|
|
496
|
+
raised in ActionCable's worker.** ActionCable runs each broadcast as its own
|
|
497
|
+
job, so one queued before the stream was stopped still ran the callback
|
|
498
|
+
against the departed component; broadcasting its changes then raised
|
|
499
|
+
`NoMethodError` on the missing connection, and the rescue meant to report
|
|
500
|
+
that raised again, so the error escaped to ActionCable's log. Such a
|
|
501
|
+
callback is now ignored (gem).
|
|
502
|
+
- **The test harness's `rendered` forgot the initial render after
|
|
503
|
+
`clear_broadcasts`.** It was rebuilt from the `_refresh` payloads still in
|
|
504
|
+
the broadcast log, so after a clear and an action it held only the parts
|
|
505
|
+
that action changed, and any inline child rendered earlier was missing.
|
|
506
|
+
Renders are now kept apart from the broadcast log, so `rendered` and
|
|
507
|
+
`rendered_html` reflect every render whatever has been cleared.
|
|
508
|
+
`broadcasts` and `dispatched_events` still start over after a clear (gem).
|
|
509
|
+
- **The test harness's `rendered` left out grandchildren.** The results
|
|
510
|
+
for inline children at every depth arrive together in the root's refresh,
|
|
511
|
+
but a child's placeholders were resolved against a set of children of its
|
|
512
|
+
own, which was always empty. A component rendered inline by an inline child
|
|
513
|
+
came out blank. Placeholders now resolve at any depth (gem).
|
|
514
|
+
- **Two components that subscribed at the same moment could end up on
|
|
515
|
+
different connections.** A socket's `LiveCable::Connection` is created the
|
|
516
|
+
first time it is needed, and ActionCable runs each subscribe as its own job
|
|
517
|
+
on its worker pool, so two components on one page subscribing together could
|
|
518
|
+
each create one. A component left on the losing one had its actions answered
|
|
519
|
+
with an `_ack` and no re-render, and shared reactive variables weren't
|
|
520
|
+
shared. The connection is now created under a lock (gem).
|
|
521
|
+
- `MethodAnalyzer` no longer raises for a component class with no Ruby source
|
|
522
|
+
location, such as one built with `Class.new`. With no methods to analyze,
|
|
523
|
+
every part that calls one of the component's methods re-renders on each
|
|
524
|
+
change (gem).
|
|
525
|
+
- `insert_root_attributes` builds a new string instead of mutating the
|
|
526
|
+
rendered part, so a frozen part can't raise `FrozenError`, and its "no root
|
|
527
|
+
element" error now shows the start of the offending output (gem).
|
|
528
|
+
- **A component destroyed while a message was in flight stayed loading.**
|
|
529
|
+
`destroy` unsubscribes the component on the client, so the reply to the
|
|
530
|
+
message that caused it never arrived and its `live-disable-with` button
|
|
531
|
+
stayed disabled. The loading state is now cleared when the server destroys
|
|
532
|
+
the component (npm).
|
|
533
|
+
|
|
534
|
+
### Added
|
|
535
|
+
|
|
536
|
+
- **`rescue_from` in components.** `ActiveSupport::Rescuable` was included but
|
|
537
|
+
never consulted. An error raised by an action, a `live-reactive` write or a
|
|
538
|
+
`stream_from` callback is now offered to the component's `rescue_from`
|
|
539
|
+
handlers first; a handled error still answers the message, so the loading
|
|
540
|
+
state clears, and anything the handler set is re-rendered. Failures while
|
|
541
|
+
subscribing or rendering still get the default error markup, as does a
|
|
542
|
+
message asking for an action the component doesn't expose or a write to a
|
|
543
|
+
variable that isn't `writable:` - those raise `LiveCable::Forbidden`, a
|
|
544
|
+
`LiveCable::Error`, and are never offered to `rescue_from` (gem).
|
|
545
|
+
- **`before_dispatch` callbacks.** Authorizing every client message meant
|
|
546
|
+
wrapping each action, keeping its arity, catching methods defined after
|
|
547
|
+
`actions`, and wrapping every writable variable's setter - which then also
|
|
548
|
+
ran for the component's own writes. `before_dispatch` runs before each
|
|
549
|
+
action call and `live-reactive` write the client sends, and before nothing
|
|
550
|
+
else: not server-side assignments, the defaults applied at subscribe, or
|
|
551
|
+
`stream_from` callbacks. `current_dispatch` says what is being dispatched
|
|
552
|
+
(`kind`, `name`, `params` or `value`). `throw :abort` skips the message and
|
|
553
|
+
still answers it, so the loading state clears; an error raised goes to
|
|
554
|
+
`rescue_from` like one raised by an action. Callbacks are inherited, so an
|
|
555
|
+
`ApplicationComponent` can declare one for every component (gem).
|
|
556
|
+
- **`live_cable_identity_tag`, so a sign-in or sign-out in a tab moves its
|
|
557
|
+
socket on.** A socket is identified once, at its handshake, and Turbo keeps
|
|
558
|
+
it open across sign-in, sign-out and impersonation, all ordinary form
|
|
559
|
+
submissions; after user A signed out and user B signed in, every component
|
|
560
|
+
in the tab kept acting as A. The helper renders a meta tag carrying a keyed
|
|
561
|
+
digest of whatever the application identifies sockets by; when a Turbo
|
|
562
|
+
visit brings a different one, the client closes the socket and opens a new
|
|
563
|
+
one before the new page's components subscribe, and they come back as the
|
|
564
|
+
new session. Other tabs, and access revoked by someone else, are the
|
|
565
|
+
application's to handle with ActionCable's
|
|
566
|
+
`remote_connections.where(...).disconnect`; the architecture guide's
|
|
567
|
+
*Sign-in, Sign-out and Revocation* shows the Devise wiring.
|
|
568
|
+
|
|
569
|
+
### Changed
|
|
570
|
+
|
|
571
|
+
- Framework warnings - a component rendered without a `.live.erb` template, a
|
|
572
|
+
missing `app/live` directory - go through the Rails logger once per process,
|
|
573
|
+
instead of `Kernel#warn` to stderr on every render (gem).
|
|
574
|
+
- RuboCop now fails CI.
|
|
575
|
+
|
|
9
576
|
## 0.3.0 - 2026-09-26
|
|
10
577
|
|
|
11
578
|
### Upgrading from 0.2
|
data/README.md
CHANGED
|
@@ -589,7 +589,7 @@ self.count.+(1)
|
|
|
589
589
|
self.name.concat("Doe")
|
|
590
590
|
```
|
|
591
591
|
|
|
592
|
-
For more details on the change tracking architecture, see [
|
|
592
|
+
For more details on the change tracking architecture, see the [architecture guide](https://livecable.io/guide/architecture).
|
|
593
593
|
|
|
594
594
|
## Shared Variables
|
|
595
595
|
|
|
@@ -649,7 +649,7 @@ module Live
|
|
|
649
649
|
end
|
|
650
650
|
```
|
|
651
651
|
|
|
652
|
-
**Use case**: FilterPanel can read the cart to show item count in a badge, but doesn't need to re-render every time an item is added—only when the filter changes.
|
|
652
|
+
**Use case**: FilterPanel can read the cart to show item count in a badge, but doesn't need to re-render every time an item is added—only when the filter changes. Whenever FilterPanel re-renders for its own reasons, parts that read `cart_items` (directly or through a method) re-render too, so the badge picks up the latest count.
|
|
653
653
|
|
|
654
654
|
## Action Whitelisting
|
|
655
655
|
|
|
@@ -897,6 +897,18 @@ Updates a reactive variable when an input changes. The corresponding reactive va
|
|
|
897
897
|
<input type="text" name="query" live-reactive="keydown keyup">
|
|
898
898
|
```
|
|
899
899
|
|
|
900
|
+
The value sent depends on the input: a checkbox sends `true` or `false` (whether it is checked, regardless of its `value` attribute), a `<select multiple>` sends an Array of the selected option values, and everything else, radio buttons included, sends its `value` as a String.
|
|
901
|
+
|
|
902
|
+
```html
|
|
903
|
+
<input type="checkbox" name="notify" live-reactive <%= 'checked' if notify %>>
|
|
904
|
+
|
|
905
|
+
<select multiple name="tags" live-reactive="change">
|
|
906
|
+
<% %w[ruby rails].each do |tag| %>
|
|
907
|
+
<option value="<%= tag %>" <%= 'selected' if tags.include?(tag) %>><%= tag %></option>
|
|
908
|
+
<% end %>
|
|
909
|
+
</select>
|
|
910
|
+
```
|
|
911
|
+
|
|
900
912
|
**Transformation:** `live-reactive` becomes `data-action="live#reactive"`, and `live-reactive="keydown"` becomes `data-action="keydown->live#reactive"`
|
|
901
913
|
|
|
902
914
|
### `live-debounce`
|