pbbuilder 0.15.1 → 0.16.0
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/.github/workflows/test.yml +1 -1
- data/Appraisals +3 -3
- data/CHANGELOG.md +7 -0
- data/Gemfile +2 -0
- data/README.md +30 -5
- data/gemfiles/rails_6.gemfile +9 -0
- data/gemfiles/rails_6_1.gemfile +1 -0
- data/gemfiles/rails_7.gemfile +9 -0
- data/gemfiles/rails_7_0.gemfile +1 -0
- data/gemfiles/rails_7_1.gemfile +10 -0
- data/lib/pbbuilder/collection_renderer.rb +30 -0
- data/lib/pbbuilder/pbbuilder.rb +4 -4
- data/lib/pbbuilder/template.rb +26 -0
- data/lib/pbbuilder.rb +37 -53
- data/pbbuilder.gemspec +1 -1
- data/test/pbbuilder_template_test.rb +30 -0
- metadata +7 -4
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24755dfd52ac20a000c27ed88c368ef1b09977cacffc1d185f59733474247043
|
4
|
+
data.tar.gz: 0dcac7ff05a509dad422976926176b921fcddcc443de24617c4218d9e5be1ada
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6aa9b3c55c5b89a85fc081a62125b7c675c22ec638fe7e29f073dcf291b87d6f0c50af69eb87371fe9c4718131b2243a230f54913f9b325e524fdbd2203107d
|
7
|
+
data.tar.gz: ecb4b6a1de74be30dc041e44f0859b8b6fab5a9e3b932503de02d53dc61b10c5e990ca0e8a631b25be2f7e92b80d6fbaa376f45dfe09e963d561a9c63a5e7a09
|
data/.github/workflows/test.yml
CHANGED
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,13 @@ 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.0
|
7
|
+
### Added
|
8
|
+
- Added support for new collection rendering, that is backed by ActiveView::CollectionRenderer.
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
- Refactoring and simplification of #merge! method without a change in functionality.
|
12
|
+
|
6
13
|
## 0.15.1
|
7
14
|
### Fixed
|
8
15
|
- #merge! method to handle repeated unintialized message object
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# Pbbuilder
|
2
2
|
PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to [JBuilder](https://rubygems.org/gems/jbuilder) gem.
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
At least Rails 6.1 is required.
|
6
|
+
|
7
|
+
## Compatibility with jBuilder
|
8
|
+
We don't aim to have 100% compitability and coverage with jbuilder gem, but we closely follow jbuilder's API design to maintain familiarity.
|
6
9
|
|
7
10
|
| | Jbuilder | Pbbuilder |
|
8
11
|
|---|---|---|
|
@@ -10,12 +13,14 @@ We don't aim to have 100% compatibility with jbuilder gem, but we closely follow
|
|
10
13
|
| cache! | ✅ | ✅ |
|
11
14
|
| cache_if! | ✅ | ✅ |
|
12
15
|
| cache_root! | ✅| |
|
16
|
+
| collection cache | ✅| |
|
13
17
|
| extract! | ✅ | ✅ |
|
14
18
|
| merge! | ✅ | ✅ |
|
15
|
-
| deep_format_keys! | ✅ | |
|
16
19
|
| child! | ✅ | |
|
17
20
|
| array! | ✅ | |
|
18
|
-
|
|
21
|
+
| .call | ✅ | |
|
22
|
+
|
23
|
+
Due to protobuf message implementation, there is absolutely no need to implement support for `deep_format_keys!`, `key_format!`, `key_format`, `deep_format_keys`, `ignore_nil!`, `ignore_nil!`, `nil`. So those would never be added.
|
19
24
|
|
20
25
|
## Usage
|
21
26
|
The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
|
@@ -76,8 +81,26 @@ Here is way to use partials with collection while passing a variable to it
|
|
76
81
|
pb.accounts @accounts, partial: "account", as: account
|
77
82
|
```
|
78
83
|
|
84
|
+
## Collections (or Arrays)
|
85
|
+
There are two different methods to render a collection. One that uses ActiveView::CollectionRenderer
|
86
|
+
```ruby
|
87
|
+
pb.friends partial: "racers/racer", as: :racer, collection: @racers
|
88
|
+
```
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
pb.friends "racers/racer", as: :racer, collection: @racers
|
92
|
+
```
|
93
|
+
|
94
|
+
And there are other ways, that don't use Collection Renderer (not very effective probably)
|
95
|
+
```ruby
|
96
|
+
pb.partial! @racer, racer: Racer.new(123, "Chris Harris", friends)
|
97
|
+
```
|
98
|
+
```ruby
|
99
|
+
pb.friends @friends, partial: "racers/racer", as: :racer
|
100
|
+
```
|
101
|
+
|
79
102
|
### Caching
|
80
|
-
|
103
|
+
it uses Rails.cache and works like caching in HTML templates:
|
81
104
|
|
82
105
|
```
|
83
106
|
pb.cache! "cache-key", expires_in: 10.minutes do
|
@@ -93,6 +116,8 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
|
|
93
116
|
end
|
94
117
|
```
|
95
118
|
|
119
|
+
Fragment caching support is currently in the works.
|
120
|
+
|
96
121
|
## Installation
|
97
122
|
Add this line to your application's Gemfile:
|
98
123
|
|
data/gemfiles/rails_6_1.gemfile
CHANGED
data/gemfiles/rails_7_0.gemfile
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_view/renderer/collection_renderer'
|
4
|
+
|
5
|
+
class Pbbuilder
|
6
|
+
class CollectionRenderer < ::ActionView::CollectionRenderer # :nodoc:
|
7
|
+
def initialize(lookup_context, options, &scope)
|
8
|
+
super(lookup_context, options)
|
9
|
+
@scope = scope
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def build_rendered_template(content, template, layout = nil)
|
15
|
+
super(content || pb.attributes!, template)
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_rendered_collection(templates, _spacer)
|
19
|
+
pb.set!(field, templates.map(&:body))
|
20
|
+
end
|
21
|
+
|
22
|
+
def pb
|
23
|
+
@options[:locals].fetch(:pb)
|
24
|
+
end
|
25
|
+
|
26
|
+
def field
|
27
|
+
@options[:locals].fetch(:field).to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/pbbuilder/pbbuilder.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Pbbuilder = Class.new(begin
|
4
|
-
|
5
|
-
|
4
|
+
require 'active_support/proxy_object'
|
5
|
+
ActiveSupport::ProxyObject
|
6
6
|
rescue LoadError
|
7
|
-
|
8
|
-
|
7
|
+
require 'active_support/basic_object'
|
8
|
+
ActiveSupport::BasicObject
|
9
9
|
end)
|
data/lib/pbbuilder/template.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pbbuilder/collection_renderer'
|
4
|
+
|
3
5
|
# PbbuilderTemplate is an extension of Pbbuilder to be used as a Rails template
|
4
6
|
# It adds support for partials.
|
5
7
|
class PbbuilderTemplate < Pbbuilder
|
@@ -37,6 +39,30 @@ class PbbuilderTemplate < Pbbuilder
|
|
37
39
|
super(field, *args) do |element|
|
38
40
|
_set_inline_partial(element, kwargs)
|
39
41
|
end
|
42
|
+
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
|
62
|
+
|
63
|
+
CollectionRenderer
|
64
|
+
.new(@context.lookup_context, options) { |&block| _scope(message[field.to_s],&block) }
|
65
|
+
.render_collection_with_partial(collection, partial, @context, nil)
|
40
66
|
else
|
41
67
|
# pb.best_friend partial: "person", person: @best_friend
|
42
68
|
# Call set! as a submessage, passing in the kwargs as partial options
|
data/lib/pbbuilder.rb
CHANGED
@@ -40,16 +40,20 @@ class Pbbuilder
|
|
40
40
|
set!(...)
|
41
41
|
end
|
42
42
|
|
43
|
+
def attributes!
|
44
|
+
@message.to_h
|
45
|
+
end
|
46
|
+
|
43
47
|
def respond_to_missing?(field)
|
44
|
-
|
48
|
+
!!_descriptor_for_field(field)
|
45
49
|
end
|
46
50
|
|
47
51
|
def set!(field, *args, &block)
|
48
52
|
name = field.to_s
|
49
|
-
descriptor =
|
53
|
+
descriptor = _descriptor_for_field(name)
|
50
54
|
::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
|
51
55
|
|
52
|
-
if
|
56
|
+
if ::Kernel.block_given?
|
53
57
|
::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
|
54
58
|
|
55
59
|
if descriptor.label == :repeated
|
@@ -57,25 +61,28 @@ class Pbbuilder
|
|
57
61
|
::Kernel.raise ::ArgumentError, "wrong number of arguments #{args.length} (expected 1)" unless args.length == 1
|
58
62
|
collection = args.first
|
59
63
|
_append_repeated(name, descriptor, collection, &block)
|
60
|
-
|
64
|
+
else
|
65
|
+
# pb.field { pb.name "hello" }
|
66
|
+
::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
|
67
|
+
message = (@message[name] ||= _new_message_from_descriptor(descriptor))
|
68
|
+
_scope(message, &block)
|
61
69
|
end
|
62
|
-
|
63
|
-
::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
|
64
|
-
# pb.field { pb.name "hello" }
|
65
|
-
message = (@message[name] ||= _new_message_from_descriptor(descriptor))
|
66
|
-
_scope(message, &block)
|
67
70
|
elsif args.length == 1
|
68
71
|
arg = args.first
|
69
72
|
if descriptor.label == :repeated
|
70
73
|
if arg.respond_to?(:to_hash)
|
71
74
|
# pb.fields {"one" => "two"}
|
72
|
-
arg.to_hash.each
|
73
|
-
|
74
|
-
end
|
75
|
-
elsif arg.respond_to?(:to_ary)
|
75
|
+
arg.to_hash.each { |k, v| @message[name][k] = v }
|
76
|
+
elsif arg.respond_to?(:to_ary) && !descriptor.type.eql?(:message)
|
76
77
|
# pb.fields ["one", "two"]
|
77
78
|
# Using concat so it behaves the same as _append_repeated
|
79
|
+
|
78
80
|
@message[name].concat arg.to_ary
|
81
|
+
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)
|
84
|
+
|
85
|
+
args.flatten.each {|obj| @message[name].push descriptor.subtype.msgclass.new(obj)}
|
79
86
|
else
|
80
87
|
# pb.fields "one"
|
81
88
|
@message[name].push arg
|
@@ -103,10 +110,7 @@ class Pbbuilder
|
|
103
110
|
end
|
104
111
|
|
105
112
|
def extract!(element, *args)
|
106
|
-
args.each
|
107
|
-
value = element.send(arg)
|
108
|
-
@message[arg.to_s] = value
|
109
|
-
end
|
113
|
+
args.each { |arg| @message[arg.to_s] = element.send(arg) }
|
110
114
|
end
|
111
115
|
|
112
116
|
# Merges object into a protobuf message, mainly used for caching.
|
@@ -115,11 +119,11 @@ class Pbbuilder
|
|
115
119
|
def merge!(object)
|
116
120
|
::Kernel.raise Pbbuilder::MergeError.build(target!, object) unless object.class == ::Hash
|
117
121
|
|
118
|
-
object.
|
119
|
-
next if
|
122
|
+
object.each do |key, value|
|
123
|
+
next if value.respond_to?(:empty?) && value.empty?
|
120
124
|
|
121
|
-
descriptor =
|
122
|
-
::Kernel.raise ::ArgumentError, "Unknown field #{
|
125
|
+
descriptor = _descriptor_for_field(key)
|
126
|
+
::Kernel.raise ::ArgumentError, "Unknown field #{key}" if descriptor.nil?
|
123
127
|
|
124
128
|
if descriptor.label == :repeated
|
125
129
|
# optional empty fields don't show up in @message object,
|
@@ -128,50 +132,26 @@ class Pbbuilder
|
|
128
132
|
@message[key.to_s] = _new_message_from_descriptor(descriptor)
|
129
133
|
end
|
130
134
|
|
131
|
-
if
|
132
|
-
|
133
|
-
elsif
|
134
|
-
elements =
|
135
|
+
if value.respond_to?(:to_hash)
|
136
|
+
value.to_hash.each {|k, v| @message[key.to_s][k] = v}
|
137
|
+
elsif value.respond_to?(:to_ary)
|
138
|
+
elements = value.map do |obj|
|
135
139
|
descriptor.subtype ? descriptor.subtype.msgclass.new(obj) : obj
|
136
140
|
end
|
137
141
|
|
138
142
|
@message[key.to_s].replace(elements)
|
139
143
|
end
|
140
144
|
else
|
141
|
-
if
|
145
|
+
if descriptor.type == :message
|
146
|
+
@message[key.to_s] = descriptor.subtype.msgclass.new(value)
|
147
|
+
else
|
142
148
|
# pb.fields {"one" => "two"}
|
143
|
-
@message[key.to_s] = object[key]
|
144
|
-
elsif object[key].class == ::TrueClass || object[key].class == ::FalseClass
|
145
149
|
# pb.boolean true || false
|
146
|
-
@message[key.to_s] = object[key]
|
147
|
-
elsif object[key].class == ::Array
|
148
150
|
# pb.field_name do
|
149
151
|
# pb.tags ["ok", "cool"]
|
150
152
|
# end
|
151
153
|
|
152
|
-
@message[key.to_s] =
|
153
|
-
elsif object[key].class == ::Hash
|
154
|
-
if @message[key.to_s].nil?
|
155
|
-
@message[key.to_s] = _new_message_from_descriptor(descriptor)
|
156
|
-
end
|
157
|
-
|
158
|
-
object[key].each do |k, v|
|
159
|
-
if object[key][k].respond_to?(:to_hash)
|
160
|
-
if @message[key.to_s][k.to_s].nil?
|
161
|
-
descriptor = @message[key.to_s].class.descriptor.lookup(k.to_s)
|
162
|
-
@message[key.to_s][k.to_s] = _new_message_from_descriptor(descriptor)
|
163
|
-
end
|
164
|
-
|
165
|
-
_scope(@message[key.to_s][k.to_s]) { self.merge!(object[key][k]) }
|
166
|
-
elsif object[key][k].respond_to?(:to_ary)
|
167
|
-
@message[key.to_s][k.to_s].replace object[key][k]
|
168
|
-
else
|
169
|
-
# Throws an error, if we try to merge nil object into empty value.
|
170
|
-
next if object[key][k].nil? && @message[key.to_s][k.to_s].nil?
|
171
|
-
|
172
|
-
@message[key.to_s][k.to_s] = object[key][k]
|
173
|
-
end
|
174
|
-
end
|
154
|
+
@message[key.to_s] = value
|
175
155
|
end
|
176
156
|
end
|
177
157
|
end
|
@@ -184,6 +164,10 @@ class Pbbuilder
|
|
184
164
|
|
185
165
|
private
|
186
166
|
|
167
|
+
def _descriptor_for_field(field)
|
168
|
+
@message.class.descriptor.lookup(field.to_s)
|
169
|
+
end
|
170
|
+
|
187
171
|
# Appends protobuf message with existing @message object
|
188
172
|
#
|
189
173
|
# @param name string
|
data/pbbuilder.gemspec
CHANGED
@@ -30,6 +30,36 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
|
|
30
30
|
assert_equal "hello", result.name
|
31
31
|
end
|
32
32
|
|
33
|
+
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", [])]')
|
35
|
+
|
36
|
+
assert_equal 2, result.friends.count
|
37
|
+
end
|
38
|
+
|
39
|
+
test "CollectionRenderer: raises an error on a render with :layout option" do
|
40
|
+
error = assert_raises NotImplementedError do
|
41
|
+
render('pb.friends partial: "racers/racer", as: :racer, layout: "layout", collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
|
42
|
+
end
|
43
|
+
|
44
|
+
assert_equal "The `:layout' option is not supported in collection rendering.", error.message
|
45
|
+
end
|
46
|
+
|
47
|
+
test "CollectionRenderer: raises an error on a render with :spacer_template option" do
|
48
|
+
error = assert_raises NotImplementedError do
|
49
|
+
render('pb.friends partial: "racers/racer", as: :racer, spacer_template: "template", collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal "The `:spacer_template' option is not supported in collection rendering.", error.message
|
53
|
+
end
|
54
|
+
|
55
|
+
test " render collections with partial as arg" do
|
56
|
+
skip("This will be addressed in future version of a gem")
|
57
|
+
result = render('pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
|
58
|
+
|
59
|
+
assert_equal 2, result.friends.count
|
60
|
+
end
|
61
|
+
|
62
|
+
|
33
63
|
test "partial by name with top-level locals" do
|
34
64
|
result = render('pb.partial! "partial", name: "hello"')
|
35
65
|
assert_equal "hello", result.name
|
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.
|
4
|
+
version: 0.16.0
|
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-
|
11
|
+
date: 2023-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-protobuf
|
@@ -76,7 +76,6 @@ files:
|
|
76
76
|
- ".github/dependabot.yml"
|
77
77
|
- ".github/workflows/test.yml"
|
78
78
|
- ".gitignore"
|
79
|
-
- ".ruby-version"
|
80
79
|
- Appraisals
|
81
80
|
- CHANGELOG.md
|
82
81
|
- Gemfile
|
@@ -86,10 +85,14 @@ files:
|
|
86
85
|
- bin/appraisal
|
87
86
|
- bin/test
|
88
87
|
- gemfiles/.bundle/config
|
88
|
+
- gemfiles/rails_6.gemfile
|
89
89
|
- gemfiles/rails_6_1.gemfile
|
90
|
+
- gemfiles/rails_7.gemfile
|
90
91
|
- gemfiles/rails_7_0.gemfile
|
92
|
+
- gemfiles/rails_7_1.gemfile
|
91
93
|
- gemfiles/rails_head.gemfile
|
92
94
|
- lib/pbbuilder.rb
|
95
|
+
- lib/pbbuilder/collection_renderer.rb
|
93
96
|
- lib/pbbuilder/errors.rb
|
94
97
|
- lib/pbbuilder/handler.rb
|
95
98
|
- lib/pbbuilder/pbbuilder.rb
|
@@ -120,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
123
|
- !ruby/object:Gem::Version
|
121
124
|
version: '0'
|
122
125
|
requirements: []
|
123
|
-
rubygems_version: 3.
|
126
|
+
rubygems_version: 3.4.20
|
124
127
|
signing_key:
|
125
128
|
specification_version: 4
|
126
129
|
summary: Generate Protobuf Messages with a simple DSL similar to JBuilder
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
3.1.2
|