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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc165db83a23b9c5bf32a6921b3cd959171083d48f7a6887df8c524f9920d574
4
- data.tar.gz: 7d3631f19dea340592918300b66e6d024889fe2c18776c729d4bebdb9424ad4b
3
+ metadata.gz: 24755dfd52ac20a000c27ed88c368ef1b09977cacffc1d185f59733474247043
4
+ data.tar.gz: 0dcac7ff05a509dad422976926176b921fcddcc443de24617c4218d9e5be1ada
5
5
  SHA512:
6
- metadata.gz: ad453cd1f12f8a71f05b98c5d60e34837848f1c4bdbe403d41a407762b980d5f47c6dc16925b01f2927fef427cb82b5a4b23801102fa7a46d280adf1245afe2f
7
- data.tar.gz: 1b0bb8019d473e1a433e3009ece6976d7a42992e00d207290106fef8a7de3fd16de5c49caaf6dc23a39aab5cdd6369caae06f62b34def0f431e6c16361210e03
6
+ metadata.gz: f6aa9b3c55c5b89a85fc081a62125b7c675c22ec638fe7e29f073dcf291b87d6f0c50af69eb87371fe9c4718131b2243a230f54913f9b325e524fdbd2203107d
7
+ data.tar.gz: ecb4b6a1de74be30dc041e44f0859b8b6fab5a9e3b932503de02d53dc61b10c5e990ca0e8a631b25be2f7e92b80d6fbaa376f45dfe09e963d561a9c63a5e7a09
@@ -13,7 +13,7 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby: ["2.7", "3.0", "3.1", "3.2"]
16
+ ruby: ["3.0", "3.1", "3.2", "3.3"]
17
17
 
18
18
  gemfile: [ "rails_6_1", "rails_7_0"]
19
19
  experimental: [false]
data/Appraisals CHANGED
@@ -6,6 +6,6 @@ appraise "rails-7-0" do
6
6
  gem "rails", "~> 7.0.0"
7
7
  end
8
8
 
9
- appraise "rails-head" do
10
- gem "rails", github: "rails/rails", branch: "main"
11
- end
9
+ appraise "rails-7-1" do
10
+ gem "rails", "~> 7.1.0"
11
+ end
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
@@ -7,3 +7,5 @@ gemspec
7
7
 
8
8
  gem "rake"
9
9
  gem "appraisal"
10
+
11
+ gem "ruby-lsp"
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
- ## Compatibility
5
- We don't aim to have 100% compatibility with jbuilder gem, but we closely follow jbuilder's API design.
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
- | ignore_nil! | ✅ | |
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
- Fragment caching is supported, it uses Rails.cache and works like caching in HTML templates:
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
 
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "appraisal"
7
+ gem "rails", "~> 6.0"
8
+
9
+ gemspec path: "../"
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
+ gem "ruby-lsp"
7
8
  gem "rails", "~> 6.1.0"
8
9
 
9
10
  gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "appraisal"
7
+ gem "rails", "~> 7.0"
8
+
9
+ gemspec path: "../"
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rake"
6
6
  gem "appraisal"
7
+ gem "ruby-lsp"
7
8
  gem "rails", "~> 7.0.0"
8
9
 
9
10
  gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake"
6
+ gem "appraisal"
7
+ gem "ruby-lsp"
8
+ gem "rails", "~> 7.1.0"
9
+
10
+ gemspec path: "../"
@@ -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
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Pbbuilder = Class.new(begin
4
- require 'active_support/proxy_object'
5
- ActiveSupport::ProxyObject
4
+ require 'active_support/proxy_object'
5
+ ActiveSupport::ProxyObject
6
6
  rescue LoadError
7
- require 'active_support/basic_object'
8
- ActiveSupport::BasicObject
7
+ require 'active_support/basic_object'
8
+ ActiveSupport::BasicObject
9
9
  end)
@@ -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
- !!@message.class.descriptor.lookup(field.to_s)
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 = @message.class.descriptor.lookup(name)
53
+ descriptor = _descriptor_for_field(name)
50
54
  ::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
51
55
 
52
- if block
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
- return
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 do |k, v|
73
- @message[name][k] = v
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 do |arg|
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.each_key do |key|
119
- next if object[key].respond_to?(:empty?) && object[key].empty?
122
+ object.each do |key, value|
123
+ next if value.respond_to?(:empty?) && value.empty?
120
124
 
121
- descriptor = @message.class.descriptor.lookup(key.to_s)
122
- ::Kernel.raise ::ArgumentError, "Unknown field #{name}" if descriptor.nil?
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 object[key].respond_to?(:to_hash)
132
- object[key].to_hash.each {|k, v| @message[key.to_s][k] = v}
133
- elsif object[key].respond_to?(:to_ary)
134
- elements = object[key].map do |obj|
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 object[key].class == ::String
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] = object[key]
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "pbbuilder"
5
- spec.version = "0.15.1"
5
+ spec.version = "0.16.0"
6
6
  spec.authors = ["Bouke van der Bijl"]
7
7
  spec.email = ["bouke@cheddar.me"]
8
8
  spec.homepage = "https://github.com/cheddar-me/pbbuilder"
@@ -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.15.1
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-06-07 00:00:00.000000000 Z
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.3.7
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