pbbuilder 0.15.1 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|