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