pbbuilder 0.17.0 → 0.19.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 +4 -13
- data/Appraisals +3 -3
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -2
- data/README.md +19 -13
- data/Rakefile +22 -5
- data/gemfiles/rails_6_1.gemfile +0 -1
- data/gemfiles/rails_7_0.gemfile +0 -1
- data/gemfiles/{rails_7_1.gemfile → rails_7_2.gemfile} +1 -2
- data/lib/pbbuilder/errors.rb +2 -4
- data/lib/pbbuilder/template.rb +19 -20
- data/lib/pbbuilder.rb +41 -17
- data/pbbuilder.gemspec +1 -1
- data/test/pbbuilder_template_test.rb +2 -2
- data/test/pbbuilder_test.rb +18 -1
- data/test/test_helper.rb +6 -0
- metadata +4 -5
- data/lib/pbbuilder/pbbuilder.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ea3598ad1ed31172d0b3154c6968649f63073f23de0d94abf191a07dbdbf4e0
|
4
|
+
data.tar.gz: 7285883ae069eeff4f1860eb9b4c4a727585e6762b3bfd974ce1fb9e51928086
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e051d79882379fb30f3f0b4bab503e6591034d1cc16639918d685a0ef19e6f7cc42ea224954bf755db739f6a2b2c2c86a8c7666e23065ce337aafb862486b4f6
|
7
|
+
data.tar.gz: 90f2266161a9a101fd4b67afb71130a009b17ec9a5139dc583db18d02a1a32a2bd7730f1ea872c3d4b77a4d64f953b1a34e72cb35f845dc6a879f117f7c3c5ec
|
data/.github/workflows/test.yml
CHANGED
@@ -14,21 +14,12 @@ jobs:
|
|
14
14
|
fail-fast: false
|
15
15
|
matrix:
|
16
16
|
ruby: ["3.0", "3.1", "3.2", "3.3"]
|
17
|
-
|
18
|
-
|
17
|
+
gemfile: [ "rails_6_1", "rails_7_0", "rails_7_2"]
|
18
|
+
exclude:
|
19
|
+
- ruby: "3.0"
|
20
|
+
gemfile: "rails_7_2"
|
19
21
|
experimental: [false]
|
20
22
|
|
21
|
-
#include:
|
22
|
-
# - ruby: '2.7'
|
23
|
-
# gemfile: rails_head
|
24
|
-
# experimental: true
|
25
|
-
# - ruby: '3.0'
|
26
|
-
# gemfile: rails_head
|
27
|
-
# experimental: true
|
28
|
-
# - ruby: '3.1'
|
29
|
-
# gemfile: rails_head
|
30
|
-
# experimental: true
|
31
|
-
|
32
23
|
steps:
|
33
24
|
- uses: actions/checkout@v3
|
34
25
|
|
data/Appraisals
CHANGED
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.19.0
|
7
|
+
### Added
|
8
|
+
- Add support for rails 7.2, but leave out rails 7.1 support. This is because ActionView has a breaking bug in 7.1 that renders the template back as a string
|
9
|
+
instead of an object, like we need for Pbbuilder https://github.com/rails/rails/pull/51023
|
10
|
+
|
11
|
+
## 0.18.0
|
12
|
+
### Added
|
13
|
+
- Allow literal assignment of protos to fields
|
14
|
+
|
6
15
|
## 0.17.0
|
7
16
|
### Changed
|
8
17
|
- Instead of appending to repeated enum message, we're replacing it to avoid issues in case output will be rendered twice
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
# Pbbuilder
|
2
|
-
PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to [JBuilder](https://rubygems.org/gems/jbuilder) gem.
|
2
|
+
PBBuilder generates [Protobuf](https://developers.google.com/protocol-buffers) Messages with a simple DSL similar to the [JBuilder](https://rubygems.org/gems/jbuilder) gem.
|
3
3
|
|
4
4
|
|
5
|
-
At least Rails 6.1 is required.
|
5
|
+
At least Rails 6.1 is required and rails 7.1 is currently not supported.
|
6
|
+
There currently is a regression in ActionView (the part of Rails which renders) that forces rendered objects into strings, but for Pbbuilder we need the raw objects.
|
7
|
+
This is only present in Rails 7.1, and a fix is released in Rails 7.2. https://github.com/rails/rails/pull/51023
|
6
8
|
|
7
9
|
## Compatibility with jBuilder
|
8
10
|
We don't aim to have 100% compitability and coverage with jbuilder gem, but we closely follow jbuilder's API design to maintain familiarity.
|
9
11
|
|
10
12
|
| | Jbuilder | Pbbuilder |
|
11
13
|
|---|---|---|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
| set! | ✅ | ✅ |
|
15
|
+
| cache! | ✅ | ✅ |
|
16
|
+
| cache_if! | ✅ | ✅ |
|
15
17
|
| cache_root! | ✅| |
|
16
18
|
| fragment cache | ✅| ✅ |
|
17
19
|
| extract! | ✅ | ✅ |
|
@@ -20,7 +22,7 @@ We don't aim to have 100% compitability and coverage with jbuilder gem, but we c
|
|
20
22
|
| array! | ✅ | |
|
21
23
|
| .call | ✅ | |
|
22
24
|
|
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.
|
25
|
+
Due to the 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.
|
24
26
|
|
25
27
|
## Usage
|
26
28
|
The main difference is that it can use introspection to figure out what kind of protobuf message it needs to create.
|
@@ -56,7 +58,7 @@ pb.phone_number account.phone_number
|
|
56
58
|
pb.tag account.tag
|
57
59
|
```
|
58
60
|
|
59
|
-
|
61
|
+
can be rewritten to a shorter version with the use of `extract!`.
|
60
62
|
```
|
61
63
|
pb.extract! account, :id, :phone_number, :tag
|
62
64
|
```
|
@@ -75,7 +77,7 @@ Using partial while passing a variable to it
|
|
75
77
|
pb.account partial: "account", account: @account
|
76
78
|
```
|
77
79
|
|
78
|
-
Here is way to use partials with collection while passing a variable to it
|
80
|
+
Here is a way to use partials with a collection while passing a variable to it
|
79
81
|
|
80
82
|
```
|
81
83
|
pb.accounts @accounts, partial: "account", as: account
|
@@ -91,7 +93,7 @@ pb.friends partial: "racers/racer", as: :racer, collection: @racers
|
|
91
93
|
pb.friends "racers/racer", as: :racer, collection: @racers
|
92
94
|
```
|
93
95
|
|
94
|
-
And there are other ways, that don't use
|
96
|
+
And there are other ways, that don't use CollectionRenderer
|
95
97
|
```ruby
|
96
98
|
pb.partial! @racer, racer: Racer.new(123, "Chris Harris", friends)
|
97
99
|
```
|
@@ -100,7 +102,7 @@ pb.friends @friends, partial: "racers/racer", as: :racer
|
|
100
102
|
```
|
101
103
|
|
102
104
|
### Caching
|
103
|
-
|
105
|
+
It uses Rails.cache and works like caching in HTML templates:
|
104
106
|
|
105
107
|
```
|
106
108
|
pb.cache! "cache-key", expires_in: 10.minutes do
|
@@ -116,7 +118,7 @@ pb.cache_if! !admin?, "cache-key", expires_in: 10.minutes do
|
|
116
118
|
end
|
117
119
|
```
|
118
120
|
|
119
|
-
Fragment caching currently works through ActionView::CollectionRenderer and can be used
|
121
|
+
Fragment caching currently works through ActionView::CollectionRenderer and can only be used with the following syntax:
|
120
122
|
|
121
123
|
```ruby
|
122
124
|
pb.friends partial: "racers/racer", as: :racer, collection: @racers, cached: true
|
@@ -146,10 +148,14 @@ $ gem install pbbuilder
|
|
146
148
|
|
147
149
|
When debugging, make sure to prepend `::Kernel` to any calls such as `puts` as otherwise the code will think you're trying to add another attribute into protobuf object.
|
148
150
|
|
149
|
-
In case
|
151
|
+
In case you're looking to use breakpoints (for debugging purposes via `binding.pry` for instance), let Pbbuilder inherit from `Object` instead of `BasicObject`]
|
152
|
+
Seen in:
|
153
|
+
[Pbbuilder](lib/pbbuilder/pbbuilder.rb)
|
154
|
+
[Errors](lib/pbbuilder/errors.rb)
|
150
155
|
|
151
156
|
## Testing
|
152
|
-
Running `bundle exec appraisal rake test` locally will run entire testsuit with all
|
157
|
+
Running `bundle exec appraisal rake test` locally will run the entire testsuit with all versions of rails.
|
158
|
+
To run tests only for a certain rails version do the following `bundle exec appraisal rails-7-0 rake test`
|
153
159
|
|
154
160
|
To run only one tests from file - use `m` utility. Like this:
|
155
161
|
`bundle exec appraisal rails-7-0 m test/pbbuilder_template_test.rb:182`
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "bundler/setup"
|
4
3
|
require "bundler/gem_tasks"
|
5
4
|
require "rake/testtask"
|
6
5
|
|
@@ -11,10 +10,28 @@ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"]
|
|
11
10
|
else
|
12
11
|
Rake::TestTask.new(:test) do |t|
|
13
12
|
t.libs << "test"
|
14
|
-
t.
|
15
|
-
|
16
|
-
|
13
|
+
t.libs << "lib"
|
14
|
+
|
15
|
+
# Running specific tests with line numbers, like with rails test, is not supported by default in rake.
|
16
|
+
# By setting the TESTOPS env var we can however specify the name of a single test with underscores instead of spaces.
|
17
|
+
# So run your single test by calling for ex:
|
18
|
+
#
|
19
|
+
# rake test /Users/sebastian/projects/cheddar/rails-twirp/test/ping_controller_test.rb "uncaught errors should bubble up to the test"
|
20
|
+
|
21
|
+
file_name = ARGV[1]
|
22
|
+
test_name = ARGV[2]&.tr(" ", "_")
|
23
|
+
|
24
|
+
ENV["TESTOPTS"] = "--verbose"
|
25
|
+
|
26
|
+
t.test_files = if file_name
|
27
|
+
if test_name
|
28
|
+
ENV["TESTOPTS"] += " --name=test_#{test_name}"
|
29
|
+
end
|
30
|
+
[file_name]
|
31
|
+
else
|
32
|
+
FileList["test/**/*_test.rb"]
|
33
|
+
end
|
17
34
|
end
|
18
35
|
|
19
36
|
task default: :test
|
20
|
-
end
|
37
|
+
end
|
data/gemfiles/rails_6_1.gemfile
CHANGED
data/gemfiles/rails_7_0.gemfile
CHANGED
data/lib/pbbuilder/errors.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Pbbuilder
|
3
|
+
class Pbbuilder < BasicObject
|
6
4
|
class MergeError < ::StandardError
|
7
5
|
def self.build(current_value, updates)
|
8
6
|
message = "Can't merge #{updates.inspect} into #{current_value.inspect}"
|
9
7
|
self.new(message)
|
10
8
|
end
|
11
9
|
end
|
12
|
-
end
|
10
|
+
end
|
data/lib/pbbuilder/template.rb
CHANGED
@@ -17,6 +17,7 @@ class PbbuilderTemplate < Pbbuilder
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Render a partial. Can be called as:
|
20
|
+
#
|
20
21
|
# pb.partial! "name/of_partial", argument: 123
|
21
22
|
# pb.partial! "name/of_partial", locals: {argument: 123}
|
22
23
|
# pb.partial! partial: "name/of_partial", argument: 123
|
@@ -30,7 +31,7 @@ class PbbuilderTemplate < Pbbuilder
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
#
|
34
|
+
# Sets the value in the message field.
|
34
35
|
#
|
35
36
|
# @example
|
36
37
|
# pb.friends @friends, partial: "friend", as: :friend
|
@@ -39,31 +40,30 @@ class PbbuilderTemplate < Pbbuilder
|
|
39
40
|
# pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
|
40
41
|
|
41
42
|
def set!(field, *args, **kwargs, &block)
|
42
|
-
# If partial options are being passed, we render a submessage with a partial
|
43
|
+
# If any partial options are being passed, we render a submessage with a partial
|
43
44
|
if kwargs.has_key?(:partial)
|
44
45
|
if args.one? && kwargs.has_key?(:as)
|
45
|
-
#
|
46
|
+
# Example syntax that should end up here:
|
46
47
|
# pb.friends @friends, partial: "friend", as: :friend
|
47
48
|
# Call set! on the super class, passing in a block that renders a partial for every element
|
48
49
|
super(field, *args) do |element|
|
49
50
|
_set_inline_partial(element, kwargs)
|
50
51
|
end
|
51
52
|
elsif kwargs.has_key?(:collection) && kwargs.has_key?(:as)
|
52
|
-
#
|
53
|
+
# Example syntax that should end up here:
|
53
54
|
# pb.friends partial: "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
|
54
55
|
|
55
56
|
_render_collection_with_options(field, kwargs[:collection], kwargs)
|
56
57
|
else
|
57
|
-
#
|
58
|
+
# Example syntax that should end up here:
|
58
59
|
# pb.best_friend partial: "person", person: @best_friend
|
59
|
-
|
60
60
|
super(field, *args) do
|
61
61
|
_render_partial_with_options(kwargs)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
else
|
65
65
|
if args.one? && kwargs.has_key?(:collection) && kwargs.has_key?(:as)
|
66
|
-
#
|
66
|
+
# Example syntax that should end up here:
|
67
67
|
# pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]
|
68
68
|
_render_collection_with_options(field, kwargs[:collection], kwargs.merge(partial: args.first))
|
69
69
|
else
|
@@ -72,7 +72,7 @@ class PbbuilderTemplate < Pbbuilder
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
# Caches fragment of message. Can be called like the following:
|
75
|
+
# Caches a fragment of a message with a given cache key. Can be called like the following:
|
76
76
|
# 'pb.cache! "cache-key" do; end'
|
77
77
|
# 'pb.cache! "cache-key", expire_in: 1.min do; end'
|
78
78
|
#
|
@@ -107,16 +107,16 @@ class PbbuilderTemplate < Pbbuilder
|
|
107
107
|
|
108
108
|
private
|
109
109
|
|
110
|
-
# Uses ActionView::CollectionRenderer to render collection effectively and to use rails built
|
110
|
+
# Uses ActionView::CollectionRenderer to render the collection effectively and to use rails' built-in fragment caching support.
|
111
111
|
#
|
112
|
-
# The way recursive rendering works is that the CollectionRenderer needs to be aware of the node
|
113
|
-
# There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer
|
114
|
-
#
|
112
|
+
# The way recursive rendering works is that the CollectionRenderer needs to be aware of the node it's currently rendering and it's parent node.
|
113
|
+
# There is no need to know the entire "stack" of nodes. ActionView::CollectionRenderer will traverse to the bottom node, render it first and then go one level up in the stack.
|
114
|
+
# Rinse and repeat until the entire stack is rendered.
|
115
115
|
|
116
116
|
# CollectionRenderer uses locals[:pb] to render the partial as a protobuf message,
|
117
117
|
# but also needs locals[:pb_parent] to apply the rendered partial to the top level protobuf message.
|
118
118
|
|
119
|
-
# This logic can be found in CollectionRenderer#build_rendered_collection method that we overwrote.
|
119
|
+
# This logic can be found in the CollectionRenderer#build_rendered_collection method that we overwrote.
|
120
120
|
def _render_collection_with_options(field, collection, options)
|
121
121
|
partial = options[:partial]
|
122
122
|
|
@@ -128,15 +128,15 @@ class PbbuilderTemplate < Pbbuilder
|
|
128
128
|
options[:locals].merge!(field: field)
|
129
129
|
|
130
130
|
if options.has_key?(:layout)
|
131
|
-
raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
|
131
|
+
::Kernel.raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
|
132
132
|
end
|
133
133
|
|
134
134
|
if options.has_key?(:spacer_template)
|
135
|
-
raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
|
135
|
+
::Kernel.raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
|
136
136
|
end
|
137
137
|
|
138
138
|
CollectionRenderer
|
139
|
-
.new(@context.lookup_context, options) { |&block| _scope(message[field.to_s]
|
139
|
+
.new(@context.lookup_context, options) { |&block| _scope(message[field.to_s], &block) }
|
140
140
|
.render_collection_with_partial(collection, partial, @context, nil)
|
141
141
|
end
|
142
142
|
|
@@ -172,10 +172,9 @@ class PbbuilderTemplate < Pbbuilder
|
|
172
172
|
begin
|
173
173
|
::Rails.cache.write(key, value, options)
|
174
174
|
rescue ::SystemCallError
|
175
|
-
# In case ActiveSupport::Cache::FileStore in Rails is used as a cache,
|
176
|
-
# File.atomic_write can have a race condition and
|
177
|
-
#
|
178
|
-
# error and returning a value.
|
175
|
+
# In case `ActiveSupport::Cache::FileStore` in Rails is used as a cache,
|
176
|
+
# `File.atomic_write` can have a race condition and fails to rename temporary file.
|
177
|
+
# We're attempting to recover from that by catching this specific error and returning a value.
|
179
178
|
#
|
180
179
|
# @see https://github.com/rails/rails/pull/44151
|
181
180
|
# @see https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb#L50
|
data/lib/pbbuilder.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "pbbuilder/
|
4
|
-
require 'pbbuilder/errors'
|
3
|
+
require "pbbuilder/errors"
|
5
4
|
require "pbbuilder/protobuf_extension"
|
6
5
|
require "pbbuilder/railtie" if defined?(Rails)
|
7
6
|
|
8
|
-
|
9
|
-
# Pbbuilder makes it easy to create a protobuf message using the builder pattern
|
7
|
+
# Pbbuilder makes it easy to create a protobuf message using the builder pattern.
|
10
8
|
# It is heavily inspired by jbuilder
|
11
9
|
#
|
12
10
|
# Given this example message definition:
|
@@ -15,8 +13,10 @@ require "pbbuilder/railtie" if defined?(Rails)
|
|
15
13
|
# repeated Person friends = 2;
|
16
14
|
# }
|
17
15
|
#
|
18
|
-
# You
|
16
|
+
# You can use Pbbuilder as follows:
|
17
|
+
#
|
19
18
|
# person = RPC::Person.new
|
19
|
+
#
|
20
20
|
# Pbbuilder.new(person) do |pb|
|
21
21
|
# pb.name "Hello"
|
22
22
|
# pb.friends [1, 2, 3] do |number|
|
@@ -29,7 +29,7 @@ require "pbbuilder/railtie" if defined?(Rails)
|
|
29
29
|
# It basically works exactly like jbuilder. The main difference is that it can use introspection to figure out what kind
|
30
30
|
# of protobuf message it needs to create.
|
31
31
|
|
32
|
-
class Pbbuilder
|
32
|
+
class Pbbuilder < BasicObject
|
33
33
|
def initialize(message)
|
34
34
|
@message = message
|
35
35
|
|
@@ -48,11 +48,15 @@ class Pbbuilder
|
|
48
48
|
!!_descriptor_for_field(field)
|
49
49
|
end
|
50
50
|
|
51
|
+
# When calling for ex: response.drivers, where response is a Google::Protobuf object, 'drivers' is not a method on that. These
|
52
|
+
# methods (or messages in our case) get added here. This is of course based on what kind of message it is. Singular, an array
|
53
|
+
# (repeated) etc. with their arguments.
|
51
54
|
def set!(field, *args, &block)
|
52
55
|
name = field.to_s
|
53
56
|
descriptor = _descriptor_for_field(name)
|
54
|
-
::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
|
57
|
+
::Kernel.raise ::ArgumentError, "Unknown field: #{name}" if descriptor.nil?
|
55
58
|
|
59
|
+
# An block is used to pass on it's children
|
56
60
|
if ::Kernel.block_given?
|
57
61
|
::Kernel.raise ::ArgumentError, "can't pass block to non-message field" unless descriptor.type == :message
|
58
62
|
|
@@ -66,9 +70,11 @@ class Pbbuilder
|
|
66
70
|
# example syntax that should end up here:
|
67
71
|
# pb.field { pb.name "hello" }
|
68
72
|
::Kernel.raise ::ArgumentError, "wrong number of arguments (expected 0)" unless args.empty?
|
73
|
+
|
69
74
|
message = (@message[name] ||= _new_message_from_descriptor(descriptor))
|
70
75
|
_scope(message, &block)
|
71
76
|
end
|
77
|
+
# No block given, but with 1 argument
|
72
78
|
elsif args.length == 1
|
73
79
|
arg = args.first
|
74
80
|
if descriptor.label == :repeated
|
@@ -82,20 +88,31 @@ class Pbbuilder
|
|
82
88
|
|
83
89
|
@message[name].replace arg.to_ary
|
84
90
|
elsif arg.respond_to?(:to_ary) && descriptor.type.eql?(:message)
|
85
|
-
#
|
86
|
-
#
|
87
|
-
|
88
|
-
|
91
|
+
# pb.friends [Person.new(name: "Johnny Test"), Person.new(name: "Max Verstappen")]
|
92
|
+
#
|
93
|
+
# Accepts both objects that can be to_hash-translated into keyword arguments
|
94
|
+
# for creating a nested proto message object and actual proto message objects
|
95
|
+
# which can be assigned "as is". With "as-is" assignment the proto message can be stored
|
96
|
+
# in memory and reused
|
97
|
+
nested_messages = arg.map do |arg_member_message_or_hash|
|
98
|
+
# If the arg passed already is a proto message and is the same as the
|
99
|
+
# field expects - just use it as-is
|
100
|
+
if arg_member_message_or_hash.is_a?(descriptor.subtype.msgclass)
|
101
|
+
arg_member_message_or_hash
|
102
|
+
else
|
103
|
+
descriptor.subtype.msgclass.new(arg_member_message_or_hash)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
@message[name].replace(nested_messages)
|
89
107
|
else
|
90
108
|
# example syntax that should end up here:
|
91
109
|
# pb.fields "one"
|
92
|
-
|
93
110
|
@message[name].replace [arg]
|
94
111
|
end
|
95
112
|
else
|
96
113
|
# example syntax that should end up here:
|
97
114
|
# pb.field "value"
|
98
|
-
|
115
|
+
|
99
116
|
@message[name] = arg
|
100
117
|
end
|
101
118
|
else
|
@@ -117,6 +134,8 @@ class Pbbuilder
|
|
117
134
|
end
|
118
135
|
end
|
119
136
|
|
137
|
+
# Shorthand command for getting a few attributes from an object.
|
138
|
+
# pb.extract! racer, :name, :id, :age
|
120
139
|
def extract!(element, *args)
|
121
140
|
args.each { |arg| @message[arg.to_s] = element.send(arg) }
|
122
141
|
end
|
@@ -170,6 +189,7 @@ class Pbbuilder
|
|
170
189
|
@message
|
171
190
|
end
|
172
191
|
|
192
|
+
# @param field string
|
173
193
|
def new_message_for(field)
|
174
194
|
descriptor = _descriptor_for_field(field)
|
175
195
|
::Kernel.raise ::ArgumentError, "Unknown field #{field}" if descriptor.nil?
|
@@ -179,11 +199,14 @@ class Pbbuilder
|
|
179
199
|
|
180
200
|
private
|
181
201
|
|
202
|
+
# Lookup the field name (or 'attribute' name, for ex "best_friend") on the Google descriptor (Google::Protobuf::Descriptor) of our
|
203
|
+
# message object.
|
204
|
+
# @param field string
|
182
205
|
def _descriptor_for_field(field)
|
183
206
|
@message.class.descriptor.lookup(field.to_s)
|
184
207
|
end
|
185
208
|
|
186
|
-
# Appends protobuf
|
209
|
+
# Appends protobuf objects to our 'repeated' attribute. This can create a list of items to a repeated field.
|
187
210
|
#
|
188
211
|
# @param name string
|
189
212
|
# @param descriptor Google::Protobuf::FieldDescriptor
|
@@ -199,7 +222,8 @@ class Pbbuilder
|
|
199
222
|
@message[name].push(*elements)
|
200
223
|
end
|
201
224
|
|
202
|
-
# Yields
|
225
|
+
# Yields a Protobuf object in the scope of message and provided values.
|
226
|
+
# This will 'assign' the field values as it were to the message attributes.
|
203
227
|
#
|
204
228
|
# @param message Google::Protobuf::(field_type)
|
205
229
|
def _scope(message)
|
@@ -211,7 +235,7 @@ class Pbbuilder
|
|
211
235
|
@message = old_message
|
212
236
|
end
|
213
237
|
|
214
|
-
#
|
238
|
+
# Builds up an empty protobuf message based on the given descriptor.
|
215
239
|
#
|
216
240
|
# @param descriptor Google::Protobuf::FieldDescriptor
|
217
241
|
def _new_message_from_descriptor(descriptor)
|
@@ -222,4 +246,4 @@ class Pbbuilder
|
|
222
246
|
message_class = message_descriptor.msgclass
|
223
247
|
message_class.new
|
224
248
|
end
|
225
|
-
end
|
249
|
+
end
|
data/pbbuilder.gemspec
CHANGED
@@ -57,7 +57,7 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
|
|
57
57
|
test "collection partial with fragment caching enabled" do
|
58
58
|
template = <<-PBBUILDER
|
59
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",
|
60
|
+
pb.friends partial: "racers/racer", collection: racers, cached: true, as: :racer
|
61
61
|
PBBUILDER
|
62
62
|
result = render(template)
|
63
63
|
|
@@ -83,7 +83,7 @@ class PbbuilderTemplateTest < ActiveSupport::TestCase
|
|
83
83
|
end
|
84
84
|
|
85
85
|
test "render collections with partial as arg" do
|
86
|
-
skip("This will be addressed in future version of
|
86
|
+
skip("This will be addressed in a future version of this gem")
|
87
87
|
result = render('pb.friends "racers/racer", as: :racer, collection: [Racer.new(1, "Johnny Test", []), Racer.new(2, "Max Verstappen", [])]')
|
88
88
|
|
89
89
|
assert_equal 2, result.friends.count
|
data/test/pbbuilder_test.rb
CHANGED
@@ -30,6 +30,23 @@ class PbbuilderTest < ActiveSupport::TestCase
|
|
30
30
|
assert_equal "Eggs", person.favourite_foods["Breakfast"]
|
31
31
|
end
|
32
32
|
|
33
|
+
test "allows assignment of prefab nested proto messages" do
|
34
|
+
max_proto = API::Person.new(name: "Max Verstappen")
|
35
|
+
james_proto = API::Person.new(name: "James Hunt")
|
36
|
+
friend_protos = [max_proto, james_proto]
|
37
|
+
|
38
|
+
person = Pbbuilder.new(API::Person.new) do |pb|
|
39
|
+
pb.name "Niki Lauda"
|
40
|
+
pb.friends friend_protos
|
41
|
+
pb.best_friend james_proto
|
42
|
+
end.target!
|
43
|
+
|
44
|
+
assert_equal "Niki Lauda", person.name
|
45
|
+
assert_equal "Max Verstappen", person.friends[0].name
|
46
|
+
assert_equal "James Hunt", person.friends[1].name
|
47
|
+
assert_equal "James Hunt", person.best_friend.name
|
48
|
+
end
|
49
|
+
|
33
50
|
test "replaces the repeated field's value with the last one set" do
|
34
51
|
person = Pbbuilder.new(API::Person.new) do |pb|
|
35
52
|
pb.field_mask do
|
@@ -45,7 +62,7 @@ class PbbuilderTest < ActiveSupport::TestCase
|
|
45
62
|
|
46
63
|
assert_equal(["ok", "that's"], p.field_mask.paths)
|
47
64
|
end
|
48
|
-
|
65
|
+
|
49
66
|
test "sets the last value of the repeated field to be the only value" do
|
50
67
|
person = Pbbuilder.new(API::Person.new) do |pb|
|
51
68
|
pb.field_mask do
|
data/test/test_helper.rb
CHANGED
@@ -55,10 +55,16 @@ class << Rails
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
Pbbuilder::CollectionRenderer.collection_cache = Rails.cache
|
59
|
+
|
58
60
|
class Racer < Struct.new(:id, :name, :friends, :best_friend, :logo)
|
59
61
|
extend ActiveModel::Naming
|
60
62
|
include ActiveModel::Conversion
|
61
63
|
|
64
|
+
def cache_key
|
65
|
+
"racer-#{id}"
|
66
|
+
end
|
67
|
+
|
62
68
|
# Fragment caching needs to know, if record could be persisted. We set it to false, this is a default in ActiveModel::API.
|
63
69
|
def persisted?
|
64
70
|
false
|
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.19.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: 2024-
|
11
|
+
date: 2024-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-protobuf
|
@@ -89,13 +89,12 @@ files:
|
|
89
89
|
- gemfiles/rails_6_1.gemfile
|
90
90
|
- gemfiles/rails_7.gemfile
|
91
91
|
- gemfiles/rails_7_0.gemfile
|
92
|
-
- gemfiles/
|
92
|
+
- gemfiles/rails_7_2.gemfile
|
93
93
|
- gemfiles/rails_head.gemfile
|
94
94
|
- lib/pbbuilder.rb
|
95
95
|
- lib/pbbuilder/collection_renderer.rb
|
96
96
|
- lib/pbbuilder/errors.rb
|
97
97
|
- lib/pbbuilder/handler.rb
|
98
|
-
- lib/pbbuilder/pbbuilder.rb
|
99
98
|
- lib/pbbuilder/protobuf_extension.rb
|
100
99
|
- lib/pbbuilder/railtie.rb
|
101
100
|
- lib/pbbuilder/template.rb
|
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
122
|
- !ruby/object:Gem::Version
|
124
123
|
version: '0'
|
125
124
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
125
|
+
rubygems_version: 3.5.18
|
127
126
|
signing_key:
|
128
127
|
specification_version: 4
|
129
128
|
summary: Generate Protobuf Messages with a simple DSL similar to JBuilder
|
data/lib/pbbuilder/pbbuilder.rb
DELETED