pbbuilder 0.16.0 → 0.16.2

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: 24755dfd52ac20a000c27ed88c368ef1b09977cacffc1d185f59733474247043
4
- data.tar.gz: 0dcac7ff05a509dad422976926176b921fcddcc443de24617c4218d9e5be1ada
3
+ metadata.gz: 0f61e783abb61817e05f29772b6614314646c0fda6f24a5f73f556ebc19d0f97
4
+ data.tar.gz: 34cf6626e2f60aa50a7ee2e7278a0149557263d023dee6a758e4adf2d8e0d263
5
5
  SHA512:
6
- metadata.gz: f6aa9b3c55c5b89a85fc081a62125b7c675c22ec638fe7e29f073dcf291b87d6f0c50af69eb87371fe9c4718131b2243a230f54913f9b325e524fdbd2203107d
7
- data.tar.gz: ecb4b6a1de74be30dc041e44f0859b8b6fab5a9e3b932503de02d53dc61b10c5e990ca0e8a631b25be2f7e92b80d6fbaa376f45dfe09e963d561a9c63a5e7a09
6
+ metadata.gz: 15f60555cb89a70e0b28490db9bc00e4b9ca7ed34f179a5df0576d6a70ef737c9d7acc66e686b0b0216a003e043c967162d377087a443f3057470e4396889e4e
7
+ data.tar.gz: fd6e5587bd3d8bafc4988f089901afe2848c577243b5d9b105e629b92c3f7cbc522fbe218f9be59ca29605af27c034c423f244c8df0208dd72d9b67bf4831143
data/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.16.2
7
+ ### Added
8
+ - Add support for partial as a first argument , e.g.`pb.friends "racers/racer", as: :racer, collection: @racers`
9
+ - Add tests to verify that fragment caching is operational
10
+
11
+ ## 0.16.1
12
+ ### Changed
13
+ - Deal properly with recursive protobuf messages while using ActiveView::CollectionRenderer
14
+
6
15
  ## 0.16.0
7
16
  ### Added
8
17
  - Added support for new collection rendering, that is backed by ActiveView::CollectionRenderer.
data/README.md CHANGED
@@ -13,7 +13,7 @@ We don't aim to have 100% compitability and coverage with jbuilder gem, but we c
13
13
  | cache! | ✅ | ✅ |
14
14
  | cache_if! | ✅ | ✅ |
15
15
  | cache_root! | ✅| |
16
- | collection cache | ✅| |
16
+ | fragment cache | ✅||
17
17
  | extract! | ✅ | ✅ |
18
18
  | merge! | ✅ | ✅ |
19
19
  | child! | ✅ | |
@@ -116,7 +116,15 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
116
116
  end
117
117
  ```
118
118
 
119
- Fragment caching support is currently in the works.
119
+ Fragment caching currently works through ActionView::CollectionRenderer and can be used only with the following syntax:
120
+
121
+ ```ruby
122
+ pb.friends partial: "racers/racer", as: :racer, collection: @racers, cached: true
123
+ ```
124
+
125
+ ```ruby
126
+ pb.friends "racers/racer", as: :racer, collection: @racers, cached: true
127
+ ```
120
128
 
121
129
  ## Installation
122
130
  Add this line to your application's Gemfile:
@@ -16,13 +16,17 @@ class Pbbuilder
16
16
  end
17
17
 
18
18
  def build_rendered_collection(templates, _spacer)
19
- pb.set!(field, templates.map(&:body))
19
+ pb_parent.set!(field, templates.map(&:body))
20
20
  end
21
21
 
22
22
  def pb
23
23
  @options[:locals].fetch(:pb)
24
24
  end
25
25
 
26
+ def pb_parent
27
+ @options[:locals].fetch(:pb_parent)
28
+ end
29
+
26
30
  def field
27
31
  @options[:locals].fetch(:field).to_s
28
32
  end
@@ -30,48 +30,45 @@ class PbbuilderTemplate < Pbbuilder
30
30
  end
31
31
  end
32
32
 
33
+ # Set the value in the message field.
34
+ #
35
+ # @example
36
+ # pb.friends @friends, partial: "friend", as: :friend
37
+ # pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
38
+ # pb.best_friend partial: "person", person: @best_friend
39
+ # pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
40
+
33
41
  def set!(field, *args, **kwargs, &block)
34
42
  # If partial options are being passed, we render a submessage with a partial
35
43
  if kwargs.has_key?(:partial)
36
44
  if args.one? && kwargs.has_key?(:as)
37
- # pb.friends @friends, partial: "friend", as: :friend
45
+ # example syntax that should end up here:
46
+ # pb.friends @friends, partial: "friend", as: :friend
38
47
  # Call set! on the super class, passing in a block that renders a partial for every element
39
48
  super(field, *args) do |element|
40
49
  _set_inline_partial(element, kwargs)
41
50
  end
42
51
  elsif kwargs.has_key?(:collection) && kwargs.has_key?(:as)
43
- # pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
44
- # collection renderer
45
- options = kwargs.deep_dup
46
-
47
- options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
48
- options.reverse_merge! ::PbbuilderTemplate.template_lookup_options
49
-
50
- collection = options[:collection] || []
51
- partial = options[:partial]
52
- options[:locals].merge!(pb: self)
53
- options[:locals].merge!(field: field)
54
-
55
- if options.has_key?(:layout)
56
- raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
57
- end
58
-
59
- if options.has_key?(:spacer_template)
60
- raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
61
- end
52
+ # example syntax that should end up here:
53
+ # pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
62
54
 
63
- CollectionRenderer
64
- .new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
65
- .render_collection_with_partial(collection, partial, @context, nil)
55
+ _render_collection_with_options(field, kwargs[:collection], kwargs)
66
56
  else
57
+ # # example syntax that should end up here:
67
58
  # pb.best_friend partial: "person", person: @best_friend
68
- # Call set! as a submessage, passing in the kwargs as partial options
59
+
69
60
  super(field, *args) do
70
61
  _render_partial_with_options(kwargs)
71
62
  end
72
63
  end
73
64
  else
74
- super
65
+ if args.one? && kwargs.has_key?(:collection) && kwargs.has_key?(:as)
66
+ # example syntax that should end up here:
67
+ # pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
68
+ _render_collection_with_options(field, kwargs[:collection], kwargs.merge(partial: args.first))
69
+ else
70
+ super
71
+ end
75
72
  end
76
73
  end
77
74
 
@@ -110,10 +107,42 @@ class PbbuilderTemplate < Pbbuilder
110
107
 
111
108
  private
112
109
 
110
+ # Uses ActionView::CollectionRenderer to render collection effectively and to use rails built in fragment caching support.
111
+ #
112
+ # The way recursive rendering works is that the CollectionRenderer needs to be aware of the node its currently rendering and parent node.
113
+ # There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer would traverse to bottom node render it first and then go one leve up in stack,
114
+ # rince and repeat until entire stack is rendered.
115
+
116
+ # CollectionRenderer uses locals[:pb] to render the partial as a protobuf message,
117
+ # but also needs locals[:pb_parent] to apply the rendered partial to the top level protobuf message.
118
+
119
+ # This logic can be found in CollectionRenderer#build_rendered_collection method that we overwrote.
120
+ def _render_collection_with_options(field, collection, options)
121
+ partial = options[:partial]
122
+
123
+ options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
124
+ options.reverse_merge! ::PbbuilderTemplate.template_lookup_options
125
+
126
+ options[:locals].merge!(pb: ::PbbuilderTemplate.new(@context, new_message_for(field)))
127
+ options[:locals].merge!(pb_parent: self)
128
+ options[:locals].merge!(field: field)
129
+
130
+ if options.has_key?(:layout)
131
+ raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
132
+ end
133
+
134
+ if options.has_key?(:spacer_template)
135
+ raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
136
+ end
137
+
138
+ CollectionRenderer
139
+ .new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
140
+ .render_collection_with_partial(collection, partial, @context, nil)
141
+ end
142
+
113
143
  # Writes to cache, if cache with keys is missing.
114
144
  #
115
145
  # @return fragment value
116
-
117
146
  def _cache_fragment_for(key, options, &block)
118
147
  key = _cache_key(key, options)
119
148
  _read_fragment_cache(key, options) || _write_fragment_cache(key, options, &block)
data/lib/pbbuilder.rb CHANGED
@@ -57,12 +57,14 @@ class Pbbuilder
57
57
  ::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
58
58
 
59
59
  if descriptor.label == :repeated
60
- # pb.field @array { |element| pb.name element.name }
60
+ # example syntax that should end up here:
61
+ # pb.field @array { |element| pb.name element.name }
61
62
  ::Kernel.raise ::ArgumentError, "wrong number of arguments #{args.length} (expected 1)" unless args.length == 1
62
63
  collection = args.first
63
64
  _append_repeated(name, descriptor, collection, &block)
64
65
  else
65
- # pb.field { pb.name "hello" }
66
+ # example syntax that should end up here:
67
+ # pb.field { pb.name "hello" }
66
68
  ::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
67
69
  message = (@message[name] ||= _new_message_from_descriptor(descriptor))
68
70
  _scope(message, &block)
@@ -71,7 +73,8 @@ class Pbbuilder
71
73
  arg = args.first
72
74
  if descriptor.label == :repeated
73
75
  if arg.respond_to?(:to_hash)
74
- # pb.fields {"one" => "two"}
76
+ # example syntax that should end up here:
77
+ # pb.fields {"one" => "two"}
75
78
  arg.to_hash.each { |k, v| @message[name][k] = v }
76
79
  elsif arg.respond_to?(:to_ary) && !descriptor.type.eql?(:message)
77
80
  # pb.fields ["one", "two"]
@@ -79,20 +82,23 @@ class Pbbuilder
79
82
 
80
83
  @message[name].concat arg.to_ary
81
84
  elsif arg.respond_to?(:to_ary) && descriptor.type.eql?(:message)
82
- # pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
83
- # Concat another Protobuf message into parent Protobuf message (not ary of strings)
85
+ # example syntax that should end up here:
86
+ # pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
84
87
 
85
88
  args.flatten.each {|obj| @message[name].push descriptor.subtype.msgclass.new(obj)}
86
89
  else
87
- # pb.fields "one"
90
+ # example syntax that should end up here:
91
+ # pb.fields "one"
88
92
  @message[name].push arg
89
93
  end
90
94
  else
91
- # pb.field "value"
95
+ # example syntax that should end up here:
96
+ # pb.field "value"
92
97
  @message[name] = arg
93
98
  end
94
99
  else
95
- # pb.field @value, :id, :name, :url
100
+ # example syntax that should end up here:
101
+ # pb.field @value, :id, :name, :url
96
102
  element = args.shift
97
103
  if descriptor.label == :repeated
98
104
  # If the message field that's being assigned is a repeated field, then we assume that `element` is enumerable.
@@ -162,6 +168,13 @@ class Pbbuilder
162
168
  @message
163
169
  end
164
170
 
171
+ def new_message_for(field)
172
+ descriptor = _descriptor_for_field(field)
173
+ ::Kernel.raise ::ArgumentError, "Unknown field #{field}" if descriptor.nil?
174
+
175
+ _new_message_from_descriptor(descriptor)
176
+ end
177
+
165
178
  private
166
179
 
167
180
  def _descriptor_for_field(field)
data/pbbuilder.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "pbbuilder"
5
- spec.version = "0.16.0"
5
+ spec.version = "0.16.2"
6
6
  spec.authors = ["Bouke van der Bijl"]
7
7
  spec.email = ["bouke@cheddar.me"]
8
8
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
@@ -12,12 +12,20 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
12
12
  pb.extract! racer, :name
13
13
  pb.friends racer.friends, partial: "racers/racer", as: :racer
14
14
  pb.best_friend partial: "racers/racer", racer: racer.best_friend if racer.best_friend.present?
15
+ pb.logo partial: "asset", asset: racer.logo if racer.logo.present?
16
+ PBBUILDER
17
+
18
+ ASSET_PARTIAL = <<-PBBUILDER
19
+ pb.url asset.url
20
+ pb.url_2x asset.url
21
+ pb.url_3x asset.url
15
22
  PBBUILDER
16
23
 
17
24
  PARTIALS = {
18
25
  "_partial.pb.pbbuilder" => "pb.name name",
19
26
  "_person.pb.pbbuilder" => PERSON_PARTIAL,
20
27
  "racers/_racer.pb.pbbuilder" => RACER_PARTIAL,
28
+ "_asset.pb.pbbuilder" => ASSET_PARTIAL,
21
29
 
22
30
  # Ensure we find only Pbbuilder partials from within Pbbuilder templates.
23
31
  "_person.html.erb" => "Hello world!"
@@ -31,9 +39,31 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
31
39
  end
32
40
 
33
41
  test "render collections with partial as kwarg" do
34
- result = render('pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
42
+ template = <<-PBBUILDER
43
+ more_friends = [Racer.new(4, "Johnny Brave", [], nil, API::Asset.new(url: "https://google.com/test3.svg"))]
44
+ friends_of_racer = [Racer.new(3, "Chris Harris", more_friends, nil, API::Asset.new(url: "https://google.com/test2.svg"))]
45
+ racers = [Racer.new(1, "Johnny Test", friends_of_racer, nil, API::Asset.new(url: "https://google.com/test1.svg")), Racer.new(2, "Max Verstappen", [])]
46
+ pb.friends partial: "racers/racer", as: :racer, collection: racers
47
+ PBBUILDER
48
+ result = render(template)
49
+
50
+ assert_equal 2, result.friends.count
51
+ assert_nil result.logo
52
+ assert_equal "https://google.com/test1.svg", result.friends.first.logo.url
53
+ assert_equal "https://google.com/test2.svg", result.friends.first.friends.first.logo.url
54
+ assert_equal "https://google.com/test3.svg", result.friends.first.friends.first.friends.first.logo.url
55
+ end
56
+
57
+ test "collection partial with fragment caching enabled" do
58
+ template = <<-PBBUILDER
59
+ racers = [Racer.new(1, "Johnny Test", [], nil, API::Asset.new(url: "https://google.com/test1.svg")), Racer.new(2, "Max Verstappen", [])]
60
+ pb.friends partial: "racers/racer", as: :racer, collection: racers, cached: true
61
+ PBBUILDER
62
+ result = render(template)
35
63
 
36
64
  assert_equal 2, result.friends.count
65
+ assert_nil result.logo
66
+ assert_equal "https://google.com/test1.svg", result.friends.first.logo.url
37
67
  end
38
68
 
39
69
  test "CollectionRenderer: raises an error on a render with :layout option" do
@@ -52,14 +82,13 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
52
82
  assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message
53
83
  end
54
84
 
55
- test " render collections with partial as arg" do
85
+ test "render collections with partial as arg" do
56
86
  skip("This will be addressed in future version of a gem")
57
87
  result = render('pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
58
88
 
59
89
  assert_equal 2, result.friends.count
60
90
  end
61
91
 
62
-
63
92
  test "partial by name with top-level locals" do
64
93
  result = render('pb.partial! "partial", name: "hello"')
65
94
  assert_equal "hello", result.name
@@ -339,9 +368,10 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
339
368
 
340
369
  view = ActionView::Base.with_empty_template_cache.new(lookup_context, assigns, controller)
341
370
 
342
- def view.view_cache_dependencies
343
- []
344
- end
371
+ def view.view_cache_dependencies; [] end
372
+ def view.combined_fragment_cache_key(key) [ key ] end
373
+ def view.cache_fragment_name(key, *) key end
374
+ def view.fragment_name_with_digest(key) key end
345
375
 
346
376
  view
347
377
  end
data/test/test_helper.rb CHANGED
@@ -46,6 +46,7 @@ end
46
46
 
47
47
  module API
48
48
  Person = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Person").msgclass
49
+ Asset = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("pbbuildertest.Asset").msgclass
49
50
  end
50
51
 
51
52
  class << Rails
@@ -54,9 +55,14 @@ class << Rails
54
55
  end
55
56
  end
56
57
 
57
- class Racer < Struct.new(:id, :name, :friends, :best_friend)
58
+ class Racer < Struct.new(:id, :name, :friends, :best_friend, :logo)
58
59
  extend ActiveModel::Naming
59
60
  include ActiveModel::Conversion
61
+
62
+ # Fragment caching needs to know, if record could be persisted. We set it to false, this is a default in ActiveModel::API.
63
+ def persisted?
64
+ false
65
+ end
60
66
  end
61
67
 
62
68
  Mime::Type.register "application/vnd.google.protobuf", :pb, [], %w(pb)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pbbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.16.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bouke van der Bijl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-23 00:00:00.000000000 Z
11
+ date: 2024-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 3.4.20
126
+ rubygems_version: 3.5.3
127
127
  signing_key:
128
128
  specification_version: 4
129
129
  summary: Generate Protobuf Messages with a simple DSL similar to JBuilder