props_template 0.16.0 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|