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 +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
|