props_template 0.37.0 → 0.38.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecdcee486c14966bbb3259a5b213631b3cd29f14a31a7194a8e9aad1ade31398
4
- data.tar.gz: c33d2129e1aa5ba580003c834f828bc48c820120e4ffa66f9eebfc1531fed7a0
3
+ metadata.gz: 3667a1f94d254d08bf49d419e9c8d9c8fa72a4d5c4b4420c1caf65e3dbbf3e1c
4
+ data.tar.gz: 2a6bda4d99a4ce0aef62debf10ebbbe75399ea065390b5cd55a6208a9ff0d97b
5
5
  SHA512:
6
- metadata.gz: 6f11a45290e1c10aa7b526506f305b723928e8414117a6aac637dbdbd95d09ae414cb18ff5c8d74a249cf12bf85f55af5d2d9f83f7846c2c03690fde5d4990bc
7
- data.tar.gz: bc476b0780f7448883bd7dfe8316ac837c851d7255420eeb8496730285d68404b64dcf94a45e856acbaefa9788c76d7f1b70722a61316129e9751a16250e8d7c
6
+ metadata.gz: bc2851e095c1a83749d16b8256915ea76a1e1b76e6dd945f893030912b8920a45f6d7c8ee7722a0abd1d8eddf021d4884bdf4b19d74728df25ac85885b333e97
7
+ data.tar.gz: 3a18ecfd1c477c87e7a7ee267db634bd750c6c6b4a2804478ce850c9e0a633215bc9d33765b4487245d2f4b017e5826830cde72ba54387b53443eb0dcd79b437
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, Deferements, and Caching can only be
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. Its best used for things like a shared header or footer.
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 an `defer: :auto` option that you can use with [SuperglueJS][1]. [SuperglueJS][1]
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 deferements is to use the index of the collection to
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` options will
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], SuperglueJS will, it will automatically kick off
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 it the node is found.
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 explicity with your keys,
645
- it makes your views quicker and more easily diggable when working in Javascript land.
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
 
@@ -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
- # todo, add ability to define contents of array
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
- handle_collection(collection, options) do |item, index|
100
- yield item, index
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
@@ -10,6 +10,10 @@ module Props
10
10
  super()
11
11
  end
12
12
 
13
+ def disable_deferments!
14
+ @em.disable_deferments
15
+ end
16
+
13
17
  def deferred!
14
18
  @em.deferred
15
19
  end
@@ -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
- options = @deferment.refine_options(options, item)
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)
@@ -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
+
@@ -8,6 +8,7 @@ module Props
8
8
  ActionView::Template.register_template_handler :props, Props::Handler
9
9
  require "props_template/dependency_tracker"
10
10
  require "props_template/layout_patch"
11
+ require "props_template/partial_patch"
11
12
  end
12
13
  end
13
14
 
@@ -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
- key_index = @search_path[@depth]
67
- id_name, id_val = key_index.to_s.split("=")
68
-
69
- if id_val
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
- index = id_name.to_i
74
- item = collection.member_at(index)
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
- if item
78
- pass_opts = @partialer.refine_options(options, item)
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 = pass_opts
83
- @found_block = proc {
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
- if pass_opts[:partial]
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
@@ -1,3 +1,3 @@
1
1
  module Props
2
- VERSION = "0.37.0".freeze
2
+ VERSION = "0.38.0".freeze
3
3
  end
@@ -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.37.0
4
+ version: 0.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-12-31 00:00:00.000000000 Z
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