props_template 0.37.0 → 1.0.0.alpha
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/README.md +25 -25
- data/lib/props_template/base.rb +32 -4
- data/lib/props_template/base_with_extensions.rb +4 -0
- data/lib/props_template/extension_manager.rb +14 -6
- data/lib/props_template/extensions/deferment.rb +6 -1
- data/lib/props_template/extensions/fragment.rb +4 -4
- data/lib/props_template/extensions/partial_renderer.rb +5 -0
- data/lib/props_template/partial_patch.rb +39 -0
- data/lib/props_template/railtie.rb +1 -0
- data/lib/props_template/searcher.rb +55 -22
- data/lib/props_template/version.rb +1 -1
- data/lib/props_template.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d2ef9c553bd49fd4d2a66bd8c2fc427e944b696c9eaa75f6224b5fb3627ca5e
|
4
|
+
data.tar.gz: a717254c4c10d35fb871c50476e72273a25b3ee1ab55ce239b2660633443dbef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a4ab324d50d6f14783b10405073e4cef4b80b70bebda2ce9e586927e79c3ea254cc6fd542ac1b5ea195ebb5d106ffde0fa2801a0215a917d10c32425146ec58
|
7
|
+
data.tar.gz: 2d04b6d02c08da88ac66f5d61c782a8de35e27552f1a49402a39bf14814593b755d8b785f30b42a9dea4197bbe199581f79fcca2b6641293f18d604d48297aa7
|
data/README.md
CHANGED
@@ -100,7 +100,7 @@ json.set! :authorDetails, {...options} do
|
|
100
100
|
json.set! :firstName, 'David'
|
101
101
|
end
|
102
102
|
|
103
|
-
or
|
103
|
+
# or
|
104
104
|
|
105
105
|
json.authorDetails, {...options} do
|
106
106
|
json.firstName 'David'
|
@@ -121,7 +121,7 @@ The inline form defines key and value
|
|
121
121
|
|
122
122
|
json.set! :firstName, 'David'
|
123
123
|
|
124
|
-
or
|
124
|
+
# or
|
125
125
|
|
126
126
|
json.firstName 'David'
|
127
127
|
|
@@ -138,13 +138,13 @@ The block form defines key and structure
|
|
138
138
|
|
139
139
|
```ruby
|
140
140
|
json.set! :details do
|
141
|
-
...
|
141
|
+
# ...
|
142
142
|
end
|
143
143
|
|
144
144
|
or
|
145
145
|
|
146
146
|
json.details do
|
147
|
-
...
|
147
|
+
# ...
|
148
148
|
end
|
149
149
|
```
|
150
150
|
|
@@ -199,7 +199,7 @@ end
|
|
199
199
|
|
200
200
|
| Parameter | Notes |
|
201
201
|
| :--- | :--- |
|
202
|
-
| collection | A collection that responds to `member_at` and `member_by` |
|
202
|
+
| collection | A collection that optionally responds to `member_at` and `member_by` |
|
203
203
|
| options | Additional [options](#options)|
|
204
204
|
|
205
205
|
To support [digging](#digging), any list passed
|
@@ -230,7 +230,7 @@ data = ObjectCollection.new([
|
|
230
230
|
])
|
231
231
|
|
232
232
|
json.array! data do
|
233
|
-
...
|
233
|
+
# ...
|
234
234
|
end
|
235
235
|
```
|
236
236
|
|
@@ -252,7 +252,7 @@ Then in your template:
|
|
252
252
|
|
253
253
|
```ruby
|
254
254
|
json.array! Post.all do
|
255
|
-
...
|
255
|
+
# ...
|
256
256
|
end
|
257
257
|
```
|
258
258
|
|
@@ -270,7 +270,7 @@ data = [
|
|
270
270
|
|
271
271
|
json.posts
|
272
272
|
json.array! data do
|
273
|
-
...
|
273
|
+
# ...
|
274
274
|
end
|
275
275
|
end
|
276
276
|
```
|
@@ -306,7 +306,7 @@ option.
|
|
306
306
|
`application.json.props` when first running `rails superglue:install:web`
|
307
307
|
|
308
308
|
## Options
|
309
|
-
Options Functionality such as Partials,
|
309
|
+
Options Functionality such as Partials, Deferments, and Caching can only be
|
310
310
|
set on a block. It is normal to see empty blocks.
|
311
311
|
|
312
312
|
```ruby
|
@@ -339,7 +339,7 @@ end
|
|
339
339
|
|
340
340
|
Rendering partials without a key is also supported using `json.partial!`, but use
|
341
341
|
sparingly! `json.partial!` is not optimized for collection rendering and may
|
342
|
-
cause performance problems.
|
342
|
+
cause performance problems. It's best used for things like a shared header or footer.
|
343
343
|
|
344
344
|
Do:
|
345
345
|
|
@@ -359,7 +359,7 @@ end
|
|
359
359
|
|
360
360
|
Do NOT:
|
361
361
|
|
362
|
-
```
|
362
|
+
```ruby
|
363
363
|
@post.each do |post|
|
364
364
|
json.partial! partial: "post", locals: {post: @post} do
|
365
365
|
end
|
@@ -406,12 +406,12 @@ json.author(cache: "some_cache_key") do
|
|
406
406
|
json.firstName "tommy"
|
407
407
|
end
|
408
408
|
|
409
|
-
#or
|
409
|
+
# or
|
410
410
|
|
411
411
|
json.profile(cache: "cachekey", partial: ["profile", locals: {foo: 1}]) do
|
412
412
|
end
|
413
413
|
|
414
|
-
#or nest it
|
414
|
+
# or nest it
|
415
415
|
|
416
416
|
json.author(cache: "some_cache_key") do
|
417
417
|
json.address(cache: "some_other_cache_key") do
|
@@ -431,7 +431,7 @@ json.array! [4,5], opts do |x|
|
|
431
431
|
json.top "hello" + x.to_s
|
432
432
|
end
|
433
433
|
|
434
|
-
#or on arrays with partials
|
434
|
+
# or on arrays with partials
|
435
435
|
|
436
436
|
opts = { cache: (->(d){ ['a', d.id] }), partial: ["blog_post", as: :blog_post] }
|
437
437
|
|
@@ -449,7 +449,7 @@ tabbed content that does not load until you click the tab.
|
|
449
449
|
When your client receives the payload, you may issue a second request to the
|
450
450
|
same endpoint to fetch any missing nodes. See [digging](#digging)
|
451
451
|
|
452
|
-
There is also
|
452
|
+
There is also a `defer: :auto` option that you can use with [SuperglueJS][1]. [SuperglueJS][1]
|
453
453
|
will use the metadata from `json.deferred!` to issue a `remote` dispatch to fetch
|
454
454
|
the missing node and immutably graft it at the appropriate keypath in your Redux
|
455
455
|
store.
|
@@ -489,10 +489,10 @@ json.defers json.deferred!
|
|
489
489
|
```
|
490
490
|
|
491
491
|
#### Working with arrays
|
492
|
-
The default behavior for
|
492
|
+
The default behavior for deferments is to use the index of the collection to
|
493
493
|
identify an element.
|
494
494
|
|
495
|
-
**Note** If you are using this library with [SuperglueJS][1], the `:auto`
|
495
|
+
**Note** If you are using this library with [SuperglueJS][1], the `:auto` option will
|
496
496
|
generate `?props_at=a.b.c.0.title` for `json.deferred!`.
|
497
497
|
|
498
498
|
If you wish to use an attribute to identify the element. You must:
|
@@ -525,7 +525,7 @@ json.posts
|
|
525
525
|
end
|
526
526
|
```
|
527
527
|
|
528
|
-
If you are using [SuperglueJS][1],
|
528
|
+
If you are using [SuperglueJS][1], it will automatically kick off
|
529
529
|
`remote(?props_at=posts.some_id=1.contact)` and `remote(?props_at=posts.some_id=2.contact)`.
|
530
530
|
|
531
531
|
## Digging
|
@@ -540,7 +540,7 @@ traversal_path = ['data', 'details', 'personal']
|
|
540
540
|
json.data(dig: traversal_path) do
|
541
541
|
json.details do
|
542
542
|
json.employment do
|
543
|
-
...more stuff
|
543
|
+
# ...more stuff
|
544
544
|
end
|
545
545
|
|
546
546
|
json.personal do
|
@@ -551,12 +551,12 @@ json.data(dig: traversal_path) do
|
|
551
551
|
end
|
552
552
|
|
553
553
|
json.footer do
|
554
|
-
...
|
554
|
+
# ...
|
555
555
|
end
|
556
556
|
```
|
557
557
|
|
558
558
|
PropsTemplate will walk depth first, walking only when it finds a matching key,
|
559
|
-
then executes the associated block, and repeats until
|
559
|
+
then executes the associated block, and repeats until the node is found.
|
560
560
|
The above will output:
|
561
561
|
|
562
562
|
```json
|
@@ -575,7 +575,7 @@ Digging only works with blocks, and will NOT work with Scalars
|
|
575
575
|
("leaf" values). For example:
|
576
576
|
|
577
577
|
```ruby
|
578
|
-
traversal_path = ['data', 'details', 'personal', 'name'] <- not found
|
578
|
+
traversal_path = ['data', 'details', 'personal', 'name'] # <- not found
|
579
579
|
|
580
580
|
json.data(dig: traversal_path) do
|
581
581
|
json.details do
|
@@ -602,7 +602,7 @@ json.data(dig: traversal_path) do
|
|
602
602
|
end
|
603
603
|
|
604
604
|
json.footer do
|
605
|
-
...
|
605
|
+
# ...
|
606
606
|
end
|
607
607
|
```
|
608
608
|
|
@@ -641,8 +641,8 @@ json.flash flash.to_h
|
|
641
641
|
will render Layout first, then the template when `yield json` is used.
|
642
642
|
|
643
643
|
## Change key format
|
644
|
-
By default, keys are not formatted. This is intentional. By being
|
645
|
-
it makes your views quicker and more easily diggable when working in
|
644
|
+
By default, keys are not formatted. This is intentional. By being explicit with your keys,
|
645
|
+
it makes your views quicker and more easily diggable when working in JavaScript land.
|
646
646
|
|
647
647
|
If you must change this behavior, override it in an initializer and cache the value:
|
648
648
|
|
data/lib/props_template/base.rb
CHANGED
@@ -6,6 +6,8 @@ module Props
|
|
6
6
|
|
7
7
|
class InvalidScopeForObjError < StandardError; end
|
8
8
|
|
9
|
+
class InvalidScopeForChildError < StandardError; end
|
10
|
+
|
9
11
|
class Base
|
10
12
|
def initialize(encoder = nil)
|
11
13
|
@stream = Oj::StringWriter.new(mode: :rails)
|
@@ -87,8 +89,7 @@ module Props
|
|
87
89
|
end
|
88
90
|
end
|
89
91
|
|
90
|
-
|
91
|
-
def array!(collection, options = {})
|
92
|
+
def array!(collection = nil, options = {})
|
92
93
|
if @scope.nil?
|
93
94
|
@scope = :array
|
94
95
|
@stream.push_array
|
@@ -96,8 +97,13 @@ module Props
|
|
96
97
|
raise InvalidScopeForArrayError.new("array! expects exclusive use of this block")
|
97
98
|
end
|
98
99
|
|
99
|
-
|
100
|
-
|
100
|
+
if collection.nil?
|
101
|
+
@child_index = nil
|
102
|
+
yield
|
103
|
+
else
|
104
|
+
handle_collection(collection, options) do |item, index|
|
105
|
+
yield item, index
|
106
|
+
end
|
101
107
|
end
|
102
108
|
|
103
109
|
@scope = :array
|
@@ -131,6 +137,28 @@ module Props
|
|
131
137
|
end
|
132
138
|
end
|
133
139
|
|
140
|
+
def child!(options = {})
|
141
|
+
if @scope != :array
|
142
|
+
raise InvalidScopeForChildError.new("child! can only be used in a `array!` with no arguments")
|
143
|
+
end
|
144
|
+
|
145
|
+
if !block_given?
|
146
|
+
raise ArgumentError.new("child! requires a block")
|
147
|
+
end
|
148
|
+
|
149
|
+
inner_scope = @scope
|
150
|
+
child_index = @child_index || -1
|
151
|
+
child_index += 1
|
152
|
+
|
153
|
+
# this changes the scope to nil so child in a child will break
|
154
|
+
set_block_content!(options) do
|
155
|
+
yield
|
156
|
+
end
|
157
|
+
|
158
|
+
@scope = inner_scope
|
159
|
+
@child_index = child_index
|
160
|
+
end
|
161
|
+
|
134
162
|
def result!
|
135
163
|
if @scope.nil?
|
136
164
|
@stream.push_object
|
@@ -12,9 +12,16 @@ module Props
|
|
12
12
|
@cache = Cache.new(@context)
|
13
13
|
end
|
14
14
|
|
15
|
+
def disable_deferments
|
16
|
+
@deferment.disable!
|
17
|
+
end
|
18
|
+
|
15
19
|
def refine_options(options, item = nil)
|
16
20
|
options = @partialer.refine_options(options, item)
|
17
|
-
|
21
|
+
if !@deferment.disabled
|
22
|
+
options = @deferment.refine_options(options, item)
|
23
|
+
end
|
24
|
+
|
18
25
|
Cache.refine_options(options, item)
|
19
26
|
end
|
20
27
|
|
@@ -40,7 +47,7 @@ module Props
|
|
40
47
|
def handle(options)
|
41
48
|
return yield if !has_extensions(options)
|
42
49
|
|
43
|
-
if options[:defer]
|
50
|
+
if options[:defer] && !@deferment.disabled
|
44
51
|
placeholder = @deferment.handle(options)
|
45
52
|
base.stream.push_value(placeholder)
|
46
53
|
@fragment.handle(options)
|
@@ -86,12 +93,13 @@ module Props
|
|
86
93
|
result
|
87
94
|
end
|
88
95
|
|
96
|
+
meta, raw_json = state.split("\n")
|
97
|
+
next_deferred, next_fragments = Oj.load(meta)
|
98
|
+
deferred.push(*next_deferred)
|
99
|
+
fragments.push(*next_fragments)
|
100
|
+
|
89
101
|
if !recently_cached
|
90
|
-
meta, raw_json = state.split("\n")
|
91
|
-
next_deferred, next_fragments = Oj.load(meta)
|
92
102
|
base.stream.push_json(raw_json)
|
93
|
-
deferred.push(*next_deferred)
|
94
|
-
fragments.push(*next_fragments)
|
95
103
|
end
|
96
104
|
else
|
97
105
|
yield
|
@@ -1,10 +1,15 @@
|
|
1
1
|
module Props
|
2
2
|
class Deferment
|
3
|
-
attr_reader :deferred
|
3
|
+
attr_reader :deferred, :disabled
|
4
4
|
|
5
5
|
def initialize(base, deferred = [])
|
6
6
|
@deferred = deferred
|
7
7
|
@base = base
|
8
|
+
@disabled = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def disable!
|
12
|
+
@disabled = true
|
8
13
|
end
|
9
14
|
|
10
15
|
def refine_options(options, item = nil)
|
@@ -9,16 +9,16 @@ module Props
|
|
9
9
|
|
10
10
|
def handle(options)
|
11
11
|
return if !options[:partial]
|
12
|
-
|
12
|
+
_partial_name, partial_opts = options[:partial]
|
13
13
|
fragment = partial_opts[:fragment]
|
14
14
|
|
15
15
|
if String === fragment || Symbol === fragment
|
16
|
-
|
16
|
+
key = fragment.to_s
|
17
17
|
path = @base.traveled_path.join(".")
|
18
|
-
@name =
|
18
|
+
@name =key
|
19
19
|
|
20
20
|
@fragments.push(
|
21
|
-
{
|
21
|
+
{id: key, path: path}
|
22
22
|
)
|
23
23
|
end
|
24
24
|
end
|
@@ -152,6 +152,10 @@ module Props
|
|
152
152
|
locals[as] = item
|
153
153
|
|
154
154
|
if (fragment_name = rest[:fragment])
|
155
|
+
if item && ::Proc === fragment_name
|
156
|
+
fragment_name = fragment_name.call(item)
|
157
|
+
end
|
158
|
+
|
155
159
|
rest[:fragment] = fragment_name.to_s
|
156
160
|
end
|
157
161
|
end
|
@@ -163,3 +167,4 @@ module Props
|
|
163
167
|
end
|
164
168
|
end
|
165
169
|
end
|
170
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Props
|
2
|
+
module PartialPatch
|
3
|
+
def render(partial, context, block)
|
4
|
+
template = find_template(partial, template_keys(partial))
|
5
|
+
|
6
|
+
if !block && (layout = @options[:layout])
|
7
|
+
layout = find_template(layout.to_s, template_keys(partial))
|
8
|
+
end
|
9
|
+
|
10
|
+
if template.respond_to?(:handler) && template.handler == Props::Handler && layout
|
11
|
+
render_partial_props_template(context, @locals, template, layout, block)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_partial_props_template(view, locals, template, layout, block)
|
18
|
+
ActiveSupport::Notifications.instrument(
|
19
|
+
"render_partial.action_view",
|
20
|
+
identifier: template.identifier,
|
21
|
+
layout: layout && layout.virtual_path,
|
22
|
+
locals: locals
|
23
|
+
) do |payload|
|
24
|
+
body = if layout
|
25
|
+
layout.render(view, locals, add_to_stack: !block) do |json|
|
26
|
+
template.render(view, locals)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
template.render(view, locals)
|
30
|
+
end
|
31
|
+
|
32
|
+
build_rendered_template(body, template)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActionView::PartialRenderer.prepend(Props::PartialPatch)
|
39
|
+
|
@@ -60,42 +60,75 @@ module Props
|
|
60
60
|
nil
|
61
61
|
end
|
62
62
|
|
63
|
-
def array!(collection, options = {}, &block)
|
63
|
+
def array!(collection = nil, options = {}, &block)
|
64
64
|
return if @found_block
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
id_val = id_val.to_i
|
71
|
-
item = collection.member_by(id_name, id_val)
|
66
|
+
if collection.nil?
|
67
|
+
# Handle child! mode - initialize child_index for searching
|
68
|
+
@child_index = nil
|
69
|
+
yield
|
72
70
|
else
|
73
|
-
|
74
|
-
|
71
|
+
# Original collection handling
|
72
|
+
key_index = @search_path[@depth]
|
73
|
+
id_name, id_val = key_index.to_s.split("=")
|
74
|
+
|
75
|
+
if id_val
|
76
|
+
id_val = id_val.to_i
|
77
|
+
item = collection.member_by(id_name, id_val)
|
78
|
+
else
|
79
|
+
index = id_name.to_i
|
80
|
+
item = collection.member_at(index)
|
81
|
+
end
|
82
|
+
|
83
|
+
if item
|
84
|
+
pass_opts = @partialer.refine_options(options, item)
|
85
|
+
@traveled_path.push(key_index)
|
86
|
+
|
87
|
+
if @depth == @search_path.size - 1
|
88
|
+
@found_options = pass_opts
|
89
|
+
@found_block = proc {
|
90
|
+
yield item, 0
|
91
|
+
}
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
@depth += 1
|
96
|
+
if pass_opts[:partial]
|
97
|
+
# todo: what happens when cached: true is passed?
|
98
|
+
# would there be any problems with not using the collection_rende?
|
99
|
+
@partialer.handle(pass_opts)
|
100
|
+
else
|
101
|
+
yield item, 0
|
102
|
+
end
|
103
|
+
@depth -= 1
|
104
|
+
end
|
75
105
|
end
|
106
|
+
end
|
76
107
|
|
77
|
-
|
78
|
-
|
108
|
+
def child!(options={}, &block)
|
109
|
+
return if @found_block
|
110
|
+
|
111
|
+
child_index = @child_index || -1
|
112
|
+
child_index += 1
|
113
|
+
|
114
|
+
key_index = @search_path[@depth]
|
115
|
+
target_index = key_index.to_i
|
116
|
+
|
117
|
+
if child_index == target_index
|
79
118
|
@traveled_path.push(key_index)
|
80
119
|
|
81
120
|
if @depth == @search_path.size - 1
|
82
|
-
@found_options =
|
83
|
-
@found_block =
|
84
|
-
yield item, 0
|
85
|
-
}
|
121
|
+
@found_options = {}
|
122
|
+
@found_block = block
|
86
123
|
return
|
87
124
|
end
|
88
125
|
|
89
126
|
@depth += 1
|
90
|
-
|
91
|
-
# todo: what happens when cached: true is passed?
|
92
|
-
# would there be any problems with not using the collection_rende?
|
93
|
-
@partialer.handle(pass_opts)
|
94
|
-
else
|
95
|
-
yield item, 0
|
96
|
-
end
|
127
|
+
yield
|
97
128
|
@depth -= 1
|
98
129
|
end
|
130
|
+
|
131
|
+
@child_index = child_index
|
99
132
|
end
|
100
133
|
end
|
101
134
|
end
|
data/lib/props_template.rb
CHANGED
@@ -22,10 +22,12 @@ module Props
|
|
22
22
|
self.template_lookup_options = {handlers: [:props]}
|
23
23
|
|
24
24
|
delegate :result!, :array!,
|
25
|
+
:child!,
|
25
26
|
:partial!,
|
26
27
|
:extract!,
|
27
28
|
:deferred!,
|
28
29
|
:fragments!,
|
30
|
+
:disable_deferments!,
|
29
31
|
:set_block_content!,
|
30
32
|
:traveled_path!,
|
31
33
|
to: :builder!
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: props_template
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.alpha
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johny Ho
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-07-13 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -84,6 +84,7 @@ files:
|
|
84
84
|
- lib/props_template/extensions/partial_renderer.rb
|
85
85
|
- lib/props_template/handler.rb
|
86
86
|
- lib/props_template/layout_patch.rb
|
87
|
+
- lib/props_template/partial_patch.rb
|
87
88
|
- lib/props_template/railtie.rb
|
88
89
|
- lib/props_template/searcher.rb
|
89
90
|
- lib/props_template/version.rb
|