phlex-reactive 0.2.7 → 0.2.8
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 +12 -0
- data/README.md +12 -0
- data/app/controllers/phlex/reactive/actions_controller.rb +61 -4
- data/lib/phlex/reactive/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b321b7411a892ec63333f9d85d988f7cf63609601386a03593efd736e88fb42
|
|
4
|
+
data.tar.gz: 00d40c394774eb54af634d328251f646a3fae1ac9bc0413ccbc89dd863608001
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd7a2129ec09c6b33a9f623f201037f5bc6639f7b097c95f521a6bf7ba4999b8dc8e11f53f2b922f5e617d0dfd8e2b0185cc5676e20f040a9caf97637d2d9438
|
|
7
|
+
data.tar.gz: b6e611eab9a2ab3f3d3a6afff9c4780061ba07321ddb5db89970e076facc9d7a374f7809fe8bc38958040c34303a42484eefe25109495bdfb2b6f894a597ae3e
|
data/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
31
31
|
|
|
32
32
|
### Fixed
|
|
33
33
|
|
|
34
|
+
- **Model-scoped form fields feed a nested param (issue #21).** A Rails
|
|
35
|
+
`Form(model: @invoice)` posts flat bracketed keys (`invoice[date]`,
|
|
36
|
+
`invoice[status]`) because the client keeps each input's `name` verbatim. The
|
|
37
|
+
server did exact-key matching, so a nested schema (`params: { invoice: { date:
|
|
38
|
+
:string } }`) looked for the literal key `"invoice"`, never found it, and
|
|
39
|
+
dropped the whole param. Param normalization now expands bracket notation
|
|
40
|
+
before coercion — `invoice[date]` nests under `invoice`, and
|
|
41
|
+
`items_attributes[0][qty]` becomes the Rails index-hash form the array coercer
|
|
42
|
+
already understands. A nested schema matches a normal Rails form with zero
|
|
43
|
+
field renaming, which is what makes the issue #16 nested-param types useful for
|
|
44
|
+
real forms. Pre-nested objects, plain scalars, and non-string values (a
|
|
45
|
+
checkbox boolean) pass through unchanged.
|
|
34
46
|
- **Nested reactive roots no longer leak fields (issue #15).** When a reactive
|
|
35
47
|
component is rendered inside another (both are `data-controller="reactive"`
|
|
36
48
|
roots), an action on the outer root previously swept *every* descendant named
|
data/README.md
CHANGED
|
@@ -294,6 +294,18 @@ end
|
|
|
294
294
|
Nested coercion recurses per field, drops undeclared nested keys, and accepts an
|
|
295
295
|
array as either a JSON array or a Rails index hash (`{ "0" => …, "1" => … }`).
|
|
296
296
|
|
|
297
|
+
**Model-scoped form fields just work.** A standard Rails `Form(model: @invoice)`
|
|
298
|
+
names its inputs `invoice[date]`, `invoice[status]`, … and the client posts those
|
|
299
|
+
names verbatim. A nested schema matches them with zero field renaming — the
|
|
300
|
+
endpoint expands bracket notation before coercion, so `invoice[date]` nests under
|
|
301
|
+
`invoice` and `invoice_items_attributes[0][qty]` becomes the index-hash form
|
|
302
|
+
above:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
action :save, params: {invoice: {date: :string, status: :string}}
|
|
306
|
+
# client posts { "invoice[date]": "…", "invoice[status]": "…" } → save(invoice: { date:, status: })
|
|
307
|
+
```
|
|
308
|
+
|
|
297
309
|
**Nested reactive components compose.** A reactive component rendered inside
|
|
298
310
|
another is its own root — field collection stops at nested
|
|
299
311
|
`data-controller="reactive"` roots, so an outer action collects only *its own*
|
|
@@ -201,12 +201,69 @@ module Phlex
|
|
|
201
201
|
end
|
|
202
202
|
|
|
203
203
|
# Unwrap ActionController::Parameters (or a plain Hash) to a string-keyed
|
|
204
|
-
# Hash so coercion can index it uniformly
|
|
204
|
+
# Hash so coercion can index it uniformly, then expand bracket notation so
|
|
205
|
+
# a model-scoped Rails form's flat keys nest (issue #21).
|
|
205
206
|
def to_param_hash(value)
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
flat =
|
|
208
|
+
if value.respond_to?(:to_unsafe_h)
|
|
209
|
+
value.to_unsafe_h.stringify_keys
|
|
210
|
+
elsif value.is_a?(Hash)
|
|
211
|
+
value.stringify_keys
|
|
212
|
+
else
|
|
213
|
+
return {}
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
expand_bracket_keys(flat)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# The client's #collectFields keeps a form input's name verbatim, so a
|
|
220
|
+
# Rails Form(model: @invoice) posts FLAT bracketed keys like
|
|
221
|
+
# "invoice[date]". Coercion does exact-key matching, so without this a
|
|
222
|
+
# nested schema (params: { invoice: { date: … } }) never finds "invoice"
|
|
223
|
+
# and drops everything. Expand "invoice[date]" => "2026-01-02" into
|
|
224
|
+
# { "invoice" => { "date" => "2026-01-02" } } — and "items[0][qty]" into
|
|
225
|
+
# the Rails index-hash form coerce_array already understands — deep-merging
|
|
226
|
+
# so sibling keys (invoice[date], invoice[status]) coalesce. Keys WITHOUT
|
|
227
|
+
# brackets and already-nested values pass through untouched, so a
|
|
228
|
+
# pre-nested object (issue #16) and plain scalars still work. Value types
|
|
229
|
+
# (a checkbox boolean, an explicit array) are preserved verbatim — unlike a
|
|
230
|
+
# round-trip through parse_nested_query, which only handles strings.
|
|
231
|
+
def expand_bracket_keys(flat)
|
|
232
|
+
flat.each_with_object({}) do |(key, value), out|
|
|
233
|
+
deep_assign(out, bracket_path(key), value)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# "invoice[items_attributes][0][qty]" => ["invoice", "items_attributes",
|
|
238
|
+
# "0", "qty"]. A key with no brackets is a single-element path.
|
|
239
|
+
def bracket_path(key)
|
|
240
|
+
return [key] unless key.include?("[")
|
|
241
|
+
|
|
242
|
+
head, rest = key.split("[", 2)
|
|
243
|
+
[head, *rest.scan(/[^\[\]]+/)]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Walk/create nested hashes along `path`, then merge `value` at the leaf so
|
|
247
|
+
# a bracket key and a sibling pre-nested object coalesce regardless of which
|
|
248
|
+
# arrives first ({ "invoice[date]" => …, invoice: { status: … } } keeps
|
|
249
|
+
# both). #merge_value deep-merges hash/hash collisions and otherwise lets
|
|
250
|
+
# the later value win (a bracket key colliding with a flat scalar).
|
|
251
|
+
def deep_assign(hash, path, value)
|
|
252
|
+
*parents, leaf = path
|
|
253
|
+
node = parents.reduce(hash) do |acc, segment|
|
|
254
|
+
acc[segment] = {} unless acc[segment].is_a?(Hash)
|
|
255
|
+
acc[segment]
|
|
256
|
+
end
|
|
257
|
+
node[leaf] = merge_value(node[leaf], value)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Combine an existing leaf value with a new one. Two hashes deep-merge (so
|
|
261
|
+
# bracket-expanded fields and a pre-nested object for the same key both
|
|
262
|
+
# survive); any other collision takes the new value.
|
|
263
|
+
def merge_value(existing, value)
|
|
264
|
+
return value unless existing.is_a?(Hash) && value.is_a?(Hash)
|
|
208
265
|
|
|
209
|
-
{}
|
|
266
|
+
existing.merge(value.stringify_keys) { |_k, old, new| merge_value(old, new) }
|
|
210
267
|
end
|
|
211
268
|
|
|
212
269
|
# Only components that opt into Reactive may be resolved. The signature
|