props_template 0.16.0 → 0.17.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/README.md +2 -48
- data/lib/props_template.rb +0 -1
- data/lib/props_template/base_with_extensions.rb +0 -4
- data/lib/props_template/extension_manager.rb +2 -15
- data/lib/props_template/extensions/cache.rb +2 -21
- data/lib/props_template/extensions/deferment.rb +11 -3
- data/lib/props_template/extensions/fragment.rb +4 -28
- data/lib/props_template/extensions/partial_renderer.rb +79 -31
- data/lib/props_template/searcher.rb +0 -3
- data/spec/layout_spec.rb +18 -10
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d36bdd3007d7932d78d70eaa76a69e84caea142098f8bff3a1df72b6c57bba8
|
4
|
+
data.tar.gz: 40f592a3801bc47ad1fe620845813f476df1d25a4ab08d3ae611bff7024af363
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6ec9ff34ea8fa3a99b243d6ef7c7e87da1c5c6ad5c43c570b311cd73372e70905fa6b84a7a05fe09d53435768261afd7213695a4c508cbcc10cdf7929bb8e5d
|
7
|
+
data.tar.gz: 4a58228230f74446c5c2a449f2c3ce023c47b292a4ed9bb22bae4864bdba441365b3e49e1d02d7fe857497c3dc8534e23c89b19f9ddb5d326357b623c6062f93
|
data/README.md
CHANGED
@@ -213,7 +213,7 @@ json.posts
|
|
213
213
|
end
|
214
214
|
```
|
215
215
|
|
216
|
-
PropsTemplate does not know what the elements are in your collection. The example above will be fine for [traversing](props-template.md#traversing_nodes) by index `\posts?bzq=posts.0`, but will raise a `NotImplementedError` if you
|
216
|
+
PropsTemplate does not know what the elements are in your collection. The example above will be fine for [traversing](props-template.md#traversing_nodes) by index `\posts?bzq=posts.0`, but will raise a `NotImplementedError` if you query by attribute `/posts?bzq=posts.id=1`. You may still need a delegate that implements `member_by`.
|
217
217
|
|
218
218
|
### json.deferred!
|
219
219
|
Returns all deferred nodes used by the [#deferment](#deferment) option.
|
@@ -233,16 +233,6 @@ json.fragments json.fragments!
|
|
233
233
|
|
234
234
|
This method is normally used in `application.json.props` when first generated by `rails breezy:install:web`
|
235
235
|
|
236
|
-
### json.fragment_digest!
|
237
|
-
|
238
|
-
Returns the digest of the current partial name and the locals passed. Useful for [optimistic updates](#optimistic-updates).
|
239
|
-
|
240
|
-
```ruby
|
241
|
-
# _some_partial.json.props
|
242
|
-
|
243
|
-
json.digest json.fragment_digest!
|
244
|
-
```
|
245
|
-
|
246
236
|
## Options
|
247
237
|
Functionality such as Partials, Deferements, and Caching can only be set on a block. It is normal to see empty blocks.
|
248
238
|
|
@@ -305,42 +295,6 @@ json.header partial: ["profile", fragment: 'me_header'] do
|
|
305
295
|
end
|
306
296
|
```
|
307
297
|
|
308
|
-
#### Optimisitc Updates
|
309
|
-
Breezy uses the digest generated by `fragment: true` to uniquely identify a partial across the redux state. If you need to optimistically update a fragment, use `json.fragment_digest!` to obtain the identifier in your partial, and `updateFragments` from BreezyJS.
|
310
|
-
|
311
|
-
For example:
|
312
|
-
|
313
|
-
```ruby
|
314
|
-
# _header.js.props
|
315
|
-
json.fragment_digest json.fragment_digest!
|
316
|
-
```
|
317
|
-
|
318
|
-
And in your reducer
|
319
|
-
|
320
|
-
```javacript
|
321
|
-
import {updateFragments} from 'jho406/Breezy';
|
322
|
-
|
323
|
-
switch(action.type) {
|
324
|
-
case SOME_ACTION: {
|
325
|
-
const {
|
326
|
-
fragmentDigest,
|
327
|
-
prevNode // <- the content of the _header.js.props
|
328
|
-
} = action.payload
|
329
|
-
|
330
|
-
const nextNode = {
|
331
|
-
...prevNode,
|
332
|
-
foo: 'bar'
|
333
|
-
}
|
334
|
-
|
335
|
-
return updateFragments(state, {
|
336
|
-
[fragmentDigest]: nextNode
|
337
|
-
})
|
338
|
-
}
|
339
|
-
default:
|
340
|
-
return state
|
341
|
-
}
|
342
|
-
```
|
343
|
-
|
344
298
|
### Caching
|
345
299
|
Caching is supported on any node.
|
346
300
|
|
@@ -418,7 +372,7 @@ json.defers json.deferred!
|
|
418
372
|
```
|
419
373
|
|
420
374
|
|
421
|
-
If `:manual` is used, PropsTemplate will no-op the block and will not populate `json.deferred!`. Its up to you to [
|
375
|
+
If `:manual` is used, PropsTemplate will no-op the block and will not populate `json.deferred!`. Its up to you to [query](props-template.md#traversing_nodes) to fetch the node seperately. A common usecase would be tab content that does not load until you click the tab.
|
422
376
|
|
423
377
|
#### Working with arrays
|
424
378
|
The default behavior for deferements is to use the index of the collection to identify an element. PropsTemplate will generate `?_bzq=a.b.c.0.title` in its metadata.
|
data/lib/props_template.rb
CHANGED
@@ -7,7 +7,7 @@ module Props
|
|
7
7
|
class ExtensionManager
|
8
8
|
attr_reader :base, :builder, :context
|
9
9
|
|
10
|
-
def initialize(base, defered=[], fragments=
|
10
|
+
def initialize(base, defered=[], fragments=[])
|
11
11
|
@base = base
|
12
12
|
@context = base.context
|
13
13
|
@builder = base.builder
|
@@ -36,10 +36,6 @@ module Props
|
|
36
36
|
@deferment.deferred
|
37
37
|
end
|
38
38
|
|
39
|
-
def fragment_digest
|
40
|
-
@fragment.name
|
41
|
-
end
|
42
|
-
|
43
39
|
def fragments
|
44
40
|
@fragment.fragments
|
45
41
|
end
|
@@ -59,10 +55,8 @@ module Props
|
|
59
55
|
handle_cache(options) do
|
60
56
|
base.set_block_content! do
|
61
57
|
if options[:partial]
|
62
|
-
current_digest = @fragment.name
|
63
58
|
@fragment.handle(options)
|
64
59
|
@partialer.handle(options)
|
65
|
-
@fragment.name = current_digest
|
66
60
|
else
|
67
61
|
yield
|
68
62
|
end
|
@@ -104,14 +98,7 @@ module Props
|
|
104
98
|
next_deferred, next_fragments = Oj.load(meta)
|
105
99
|
base.stream.push_json(raw_json)
|
106
100
|
deferred.push(*next_deferred)
|
107
|
-
|
108
|
-
next_fragments.each do |k, v|
|
109
|
-
if fragments[k]
|
110
|
-
fragments[k].push(*v)
|
111
|
-
else
|
112
|
-
fragments[k] = v
|
113
|
-
end
|
114
|
-
end
|
101
|
+
fragments.push(*next_fragments)
|
115
102
|
end
|
116
103
|
else
|
117
104
|
yield
|
@@ -25,12 +25,6 @@ module Props
|
|
25
25
|
@context
|
26
26
|
end
|
27
27
|
|
28
|
-
def instrument(name, **options)
|
29
|
-
ActiveSupport::Notifications.instrument(name, options) do |payload|
|
30
|
-
yield payload
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
28
|
def multi_fetch(keys, options = {})
|
35
29
|
result = {}
|
36
30
|
key_to_ckey = {}
|
@@ -49,7 +43,7 @@ module Props
|
|
49
43
|
|
50
44
|
read_caches = {}
|
51
45
|
|
52
|
-
instrument('read_multi_fragments.action_view', payload) do |payload|
|
46
|
+
ActiveSupport::Notifications.instrument('read_multi_fragments.action_view', payload) do |payload|
|
53
47
|
read_caches = ::Rails.cache.read_multi(*ckeys, options)
|
54
48
|
payload[:read_caches] = read_caches
|
55
49
|
end
|
@@ -122,7 +116,7 @@ module Props
|
|
122
116
|
|
123
117
|
def cache_key(key, options)
|
124
118
|
name_options = options.slice(:skip_digest, :virtual_path)
|
125
|
-
key =
|
119
|
+
key = @context.cache_fragment_name(key, **name_options)
|
126
120
|
|
127
121
|
if @context.respond_to?(:combined_fragment_cache_key)
|
128
122
|
key = @context.combined_fragment_cache_key(key)
|
@@ -132,19 +126,6 @@ module Props
|
|
132
126
|
|
133
127
|
::ActiveSupport::Cache.expand_cache_key(key, :props)
|
134
128
|
end
|
135
|
-
|
136
|
-
def fragment_name_with_digest(key, options)
|
137
|
-
if @context.respond_to?(:cache_fragment_name)
|
138
|
-
# Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
|
139
|
-
# should be used instead.
|
140
|
-
@context.cache_fragment_name(key, options)
|
141
|
-
elsif @context.respond_to?(:fragment_name_with_digest)
|
142
|
-
# Backwards compatibility for period of time when fragment_name_with_digest was made public.
|
143
|
-
@context.fragment_name_with_digest(key)
|
144
|
-
else
|
145
|
-
key
|
146
|
-
end
|
147
|
-
end
|
148
129
|
end
|
149
130
|
end
|
150
131
|
|
@@ -34,6 +34,8 @@ module Props
|
|
34
34
|
|
35
35
|
type, rest = options[:defer]
|
36
36
|
placeholder = rest[:placeholder]
|
37
|
+
success_action = rest[:success_action]
|
38
|
+
fail_action = rest[:fail_action]
|
37
39
|
|
38
40
|
if type.to_sym == :auto && options[:key]
|
39
41
|
key, val = options[:key]
|
@@ -50,11 +52,17 @@ module Props
|
|
50
52
|
|
51
53
|
uri.query = ::URI.encode_www_form(qry)
|
52
54
|
|
53
|
-
|
55
|
+
deferral = {
|
54
56
|
url: uri.to_s,
|
55
57
|
path: path,
|
56
|
-
type: type.to_s
|
57
|
-
|
58
|
+
type: type.to_s,
|
59
|
+
}
|
60
|
+
|
61
|
+
# camelize for JS land
|
62
|
+
deferral[:successAction] = success_action if success_action
|
63
|
+
deferral[:failAction] = fail_action if fail_action
|
64
|
+
|
65
|
+
@deferred.push(deferral)
|
58
66
|
|
59
67
|
placeholder
|
60
68
|
end
|
@@ -1,14 +1,10 @@
|
|
1
|
-
require 'digest'
|
2
|
-
|
3
1
|
module Props
|
4
2
|
class Fragment
|
5
3
|
attr_reader :fragments
|
6
|
-
attr_accessor :name
|
7
4
|
|
8
|
-
def initialize(base, fragments=
|
5
|
+
def initialize(base, fragments=[])
|
9
6
|
@base = base
|
10
7
|
@fragments = fragments
|
11
|
-
@digest = Digest::SHA2.new(256)
|
12
8
|
end
|
13
9
|
|
14
10
|
def handle(options)
|
@@ -20,30 +16,10 @@ module Props
|
|
20
16
|
fragment_name = fragment.to_s
|
21
17
|
path = @base.traveled_path.join('.')
|
22
18
|
@name = fragment_name
|
23
|
-
@fragments[fragment_name] ||= []
|
24
|
-
@fragments[fragment_name].push(path)
|
25
|
-
end
|
26
|
-
|
27
|
-
if fragment == true
|
28
|
-
locals = partial_opts[:locals]
|
29
19
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
.tap{|h| h.delete(:json)}
|
34
|
-
.each do |key, value|
|
35
|
-
if value.respond_to?(:to_global_id)
|
36
|
-
identity[key] = value.to_global_id.to_s
|
37
|
-
else
|
38
|
-
identity[key] = value
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
path = @base.traveled_path.join('.')
|
43
|
-
fragment_name = @digest.hexdigest("#{partial_name}#{identity.to_json}")
|
44
|
-
@name = fragment_name
|
45
|
-
@fragments[fragment_name] ||= []
|
46
|
-
@fragments[fragment_name].push(path)
|
20
|
+
@fragments.push(
|
21
|
+
{ type: fragment_name, partial: partial_name, path: path }
|
22
|
+
)
|
47
23
|
end
|
48
24
|
end
|
49
25
|
end
|
@@ -1,22 +1,56 @@
|
|
1
1
|
require 'action_view'
|
2
2
|
|
3
3
|
module Props
|
4
|
+
class RenderedTemplate
|
5
|
+
attr_reader :body, :layout, :template
|
6
|
+
|
7
|
+
def initialize(body, layout, template)
|
8
|
+
@body = body
|
9
|
+
@layout = layout
|
10
|
+
@template = template
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
template.format
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
4
18
|
class Partialer
|
19
|
+
INVALID_PARTIAL_MESSAGE = "The partial name must be a string, but received (%s)."
|
20
|
+
|
5
21
|
def initialize(base, context, builder)
|
6
22
|
@context = context
|
7
23
|
@builder = builder
|
8
24
|
@base = base
|
9
25
|
end
|
10
26
|
|
27
|
+
def extract_details(options) # :doc:
|
28
|
+
@context.lookup_context.registered_details.each_with_object({}) do |key, details|
|
29
|
+
value = options[key]
|
30
|
+
|
31
|
+
details[key] = Array(value) if value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
11
35
|
def find_and_add_template(all_options)
|
12
36
|
first_opts = all_options[0]
|
13
37
|
|
14
38
|
if first_opts[:partial]
|
15
39
|
partial_opts = block_opts_to_render_opts(@builder, first_opts)
|
16
|
-
|
40
|
+
.merge(formats: [:json])
|
41
|
+
partial_opts.delete(:handlers)
|
42
|
+
partial = partial_opts[:partial]
|
43
|
+
|
44
|
+
if !(String === partial)
|
45
|
+
raise ArgumentError.new(INVALID_PARTIAL_MESSAGE % (partial.inspect))
|
46
|
+
end
|
47
|
+
|
48
|
+
template_keys = retrieve_template_keys(partial_opts)
|
49
|
+
details = extract_details(partial_opts)
|
50
|
+
template = find_template(partial, template_keys, details)
|
17
51
|
|
18
52
|
all_options.map do |opts|
|
19
|
-
opts[:_template] =
|
53
|
+
opts[:_template] = template
|
20
54
|
opts
|
21
55
|
end
|
22
56
|
else
|
@@ -24,6 +58,17 @@ module Props
|
|
24
58
|
end
|
25
59
|
end
|
26
60
|
|
61
|
+
def find_template(path, locals, details)
|
62
|
+
prefixes = path.include?(?/) ? [] : @context.lookup_context.prefixes
|
63
|
+
@context.lookup_context.find_template(path, prefixes, true, locals, details)
|
64
|
+
end
|
65
|
+
|
66
|
+
def retrieve_template_keys(options)
|
67
|
+
template_keys = options[:locals].keys
|
68
|
+
template_keys << options[:as] if options[:as]
|
69
|
+
template_keys
|
70
|
+
end
|
71
|
+
|
27
72
|
def block_opts_to_render_opts(builder, options)
|
28
73
|
partial, pass_opts = [*options[:partial]]
|
29
74
|
pass_opts ||= {}
|
@@ -45,9 +90,20 @@ module Props
|
|
45
90
|
|
46
91
|
renderer.render(template, pass_opts)
|
47
92
|
end
|
93
|
+
|
94
|
+
def render(template, options)
|
95
|
+
view = @context
|
96
|
+
instrument(:partial, identifier: template.identifier) do |payload|
|
97
|
+
locals = options[:locals]
|
98
|
+
content = template.render(view, locals)
|
99
|
+
|
100
|
+
payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
|
101
|
+
build_rendered_template(content, template)
|
102
|
+
end
|
103
|
+
end
|
48
104
|
end
|
49
105
|
|
50
|
-
class PartialRenderer
|
106
|
+
class PartialRenderer
|
51
107
|
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
|
52
108
|
"make sure it starts with lowercase letter, " \
|
53
109
|
"and is followed by any combination of letters, numbers and underscores."
|
@@ -56,21 +112,6 @@ module Props
|
|
56
112
|
|
57
113
|
INVALID_PARTIAL_MESSAGE = "The partial name must be a string, but received (%s)."
|
58
114
|
|
59
|
-
def self.find_and_add_template(builder, context, all_options)
|
60
|
-
first_opts = all_options[0]
|
61
|
-
|
62
|
-
if first_opts[:partial]
|
63
|
-
partial_opts = block_opts_to_render_opts(builder, first_opts)
|
64
|
-
renderer = new(context, partial_opts)
|
65
|
-
|
66
|
-
all_options.map do |opts|
|
67
|
-
opts[:_template] = renderer.template
|
68
|
-
opts
|
69
|
-
end
|
70
|
-
else
|
71
|
-
all_options
|
72
|
-
end
|
73
|
-
end
|
74
115
|
|
75
116
|
def self.raise_invalid_option_as(as)
|
76
117
|
raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
|
@@ -106,8 +147,7 @@ module Props
|
|
106
147
|
locals[as] = item
|
107
148
|
|
108
149
|
if fragment_name = rest[:fragment]
|
109
|
-
|
110
|
-
rest[:fragment] = fragment_name
|
150
|
+
rest[:fragment] = fragment_name.to_s
|
111
151
|
end
|
112
152
|
end
|
113
153
|
|
@@ -121,7 +161,6 @@ module Props
|
|
121
161
|
|
122
162
|
def initialize(context, options)
|
123
163
|
@context = context
|
124
|
-
super(@context.lookup_context)
|
125
164
|
@options = options.merge(formats: [:json])
|
126
165
|
@options.delete(:handlers)
|
127
166
|
@details = extract_details(@options)
|
@@ -133,7 +172,7 @@ module Props
|
|
133
172
|
end
|
134
173
|
|
135
174
|
@path = partial
|
136
|
-
|
175
|
+
|
137
176
|
template_keys = retrieve_template_keys(@options)
|
138
177
|
@template = find_template(@path, template_keys)
|
139
178
|
end
|
@@ -145,6 +184,19 @@ module Props
|
|
145
184
|
end
|
146
185
|
|
147
186
|
private
|
187
|
+
def extract_details(options) # :doc:
|
188
|
+
@context.lookup_context.registered_details.each_with_object({}) do |key, details|
|
189
|
+
value = options[key]
|
190
|
+
|
191
|
+
details[key] = Array(value) if value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def instrument(name, **options) # :doc:
|
196
|
+
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
|
197
|
+
yield payload
|
198
|
+
end
|
199
|
+
end
|
148
200
|
|
149
201
|
def render_partial(template, view, options)
|
150
202
|
template ||= @template
|
@@ -159,17 +211,13 @@ module Props
|
|
159
211
|
end
|
160
212
|
end
|
161
213
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
#
|
166
|
-
# If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
|
167
|
-
# set to that string. Otherwise, the +options[:partial]+ object must
|
168
|
-
# respond to +to_partial_path+ in order to setup the path.
|
214
|
+
def build_rendered_template(content, template, layout = nil)
|
215
|
+
RenderedTemplate.new content, layout, template
|
216
|
+
end
|
169
217
|
|
170
218
|
def find_template(path, locals)
|
171
|
-
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
|
172
|
-
@lookup_context.find_template(path, prefixes, true, locals, @details)
|
219
|
+
prefixes = path.include?(?/) ? [] : @context.lookup_context.prefixes
|
220
|
+
@context.lookup_context.find_template(path, prefixes, true, locals, @details)
|
173
221
|
end
|
174
222
|
|
175
223
|
def retrieve_template_keys(options)
|
data/spec/layout_spec.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require
|
1
|
+
require_relative "./support/helper"
|
2
|
+
require_relative "./support/rails_helper"
|
3
|
+
require "props_template/layout_patch"
|
4
|
+
require "action_controller"
|
4
5
|
|
5
|
-
RSpec.describe
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
RSpec.describe "Props::Template" do
|
7
|
+
class TestController < ActionController::Base
|
8
|
+
protect_from_forgery
|
9
|
+
|
10
|
+
def self.controller_path
|
11
|
+
""
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "uses a layout to render" do
|
16
|
+
view_path = File.join(File.dirname(__FILE__), "./fixtures")
|
17
|
+
controller = TestController.new
|
9
18
|
controller.prepend_view_path(view_path)
|
10
|
-
controller.response.headers['Content-Type']='application/json'
|
11
|
-
controller.request.path = '/some_url'
|
12
19
|
|
13
|
-
json = controller.
|
20
|
+
json = controller.render_to_string("200", layout: "application")
|
21
|
+
|
14
22
|
expect(json.strip).to eql('{"data":{"success":"ok"}}')
|
15
23
|
end
|
16
24
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: props_template
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Johny Ho
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -96,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
99
|
+
rubygems_version: 3.1.2
|
100
100
|
signing_key:
|
101
101
|
specification_version: 4
|
102
102
|
summary: A JSON builder for your React props
|