pbbuilder 0.16.0 → 0.16.2
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/CHANGELOG.md +9 -0
- data/README.md +10 -2
- data/lib/pbbuilder/collection_renderer.rb +5 -1
- data/lib/pbbuilder/template.rb +55 -26
- data/lib/pbbuilder.rb +21 -8
- data/pbbuilder.gemspec +1 -1
- data/test/pbbuilder_template_test.rb +36 -6
- data/test/test_helper.rb +7 -1
- 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: 0f61e783abb61817e05f29772b6614314646c0fda6f24a5f73f556ebc19d0f97
|
4
|
+
data.tar.gz: 34cf6626e2f60aa50a7ee2e7278a0149557263d023dee6a758e4adf2d8e0d263
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
|
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
|
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
|
-
|
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
|
data/lib/pbbuilder/template.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
44
|
-
# collection
|
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
|
-
|
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
|
-
|
59
|
+
|
69
60
|
super(field, *args) do
|
70
61
|
_render_partial_with_options(kwargs)
|
71
62
|
end
|
72
63
|
end
|
73
64
|
else
|
74
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
83
|
-
#
|
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
|
-
#
|
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
|
-
#
|
95
|
+
# example syntax that should end up here:
|
96
|
+
# pb.field "value"
|
92
97
|
@message[name] = arg
|
93
98
|
end
|
94
99
|
else
|
95
|
-
#
|
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
@@ -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
|
-
|
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 "
|
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.
|
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:
|
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.
|
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
|