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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abee2de45e7fc076a1edad8c9d3edd9f13b3335e0946d6d227fcc4e5370a9e60
4
- data.tar.gz: 20bb87744d490b3cae705a1514a965b07ec04cfdc99c5f7deafb627c00fb7484
3
+ metadata.gz: 4d36bdd3007d7932d78d70eaa76a69e84caea142098f8bff3a1df72b6c57bba8
4
+ data.tar.gz: 40f592a3801bc47ad1fe620845813f476df1d25a4ab08d3ae611bff7024af363
5
5
  SHA512:
6
- metadata.gz: 84b19f30e105c344ea37f0db00524804f998ba01ab84e0cd2f5c47e7c9324061096fd9b089df310a9cfc7a25980b72b01cb2c397649d3ce6ba24e9ca1fc7fd94
7
- data.tar.gz: 07257d4609fb4ebbce6621dd804f87abe1b28ab336a41ea1d092611d973d3ea905134b5ad0d690ed9496c62758318825034b1f99b3668b09b3aa7046f49fc9c3
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 traverse by attribute `/posts?bzq=posts.id=1`. You may still need a delegate that implements `member_by`.
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 [traverse](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.
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.
@@ -16,7 +16,6 @@ module Props
16
16
  :deferred!,
17
17
  :fragments!,
18
18
  :set_block_content!,
19
- :fragment_digest!,
20
19
  to: :builder!
21
20
 
22
21
  def initialize(context = nil, options = {})
@@ -28,10 +28,6 @@ module Props
28
28
  @em.fragments
29
29
  end
30
30
 
31
- def fragment_digest!
32
- @em.fragment_digest
33
- end
34
-
35
31
  def set_block_content!(options = {})
36
32
  return super if !@em.has_extensions(options)
37
33
 
@@ -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 = fragment_name_with_digest(key, name_options)
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
- @deferred.push(
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
- identity = {}
31
- locals
32
- .clone
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
- renderer = PartialRenderer.new(@context, partial_opts)
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] = renderer.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 < ActionView::AbstractRenderer
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
- fragment_name = Proc === fragment_name ? fragment_name.call(item) : fragment_name.to_s
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
- @context_prefix = @lookup_context.prefixes.first
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
- # Sets up instance variables needed for rendering a partial. This method
163
- # finds the options and details and extracts them. The method also contains
164
- # logic that handles the type of object passed in as the partial.
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)
@@ -23,9 +23,6 @@ module Props
23
23
  []
24
24
  end
25
25
 
26
- def fragment_digest!
27
- end
28
-
29
26
  def found!
30
27
  pass_opts = @found_options.clone || {}
31
28
  pass_opts.delete(:defer)
@@ -1,16 +1,24 @@
1
- require_relative './support/helper'
2
- require_relative './support/rails_helper'
3
- require 'props_template/layout_patch'
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 'Props::Template' do
6
- it 'uses a layout to render' do
7
- view_path = File.join(File.dirname(__FILE__),'./fixtures')
8
- controller = ActionView::TestCase::TestController.new
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.render('200', layout: 'application')
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.16.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: 2020-07-25 00:00:00.000000000 Z
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.0.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