pb-serializer 0.1.0 → 0.5.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: f67d4a17784718471b0092c70c74d8ad19c3c7dcd7d0003268d0b52ee1cff8b5
4
- data.tar.gz: 970526e14d3bacab347a57fb1a556ea041529cca3e2d9ad0e5aaf4482f4808bb
3
+ metadata.gz: b46a686595d5e9e919c21ff7e6902cec71be97d716085dc38b8b2ef0192e7cf1
4
+ data.tar.gz: c453986c407e5e567bce43f3f45429697c552dedc44356ffee2a8e0b4d5eaea7
5
5
  SHA512:
6
- metadata.gz: 922d74b4ca48c70e6fc37358a994630fceb3244acb8af39523dc60b8288a25c8f4f44f45c67e663743b01800fb8ec7a60b5f6804598d698ea42531947d5cb81d
7
- data.tar.gz: 87fb4dec4bb9e073d6b461fd1b0d80803e8f1763a021e33746b9e789dfe71566dc9ccdf45ae9ca3834c4aeeb152b53660ee4bb4fedbd1525d4d81304b7f6c233
6
+ metadata.gz: c58c5ab0714bd7bee52f3f30ede95b676b849cdf0be482a9de0187287d3ecfd0e758b6cc7c17831965d7e8e54fc13763c6290607330077c3a2135c6fe9cfd439
7
+ data.tar.gz: 7b3cc2224f7550c72714cc21c1e656182891788b1f0aa67b02dce177ea44cb6dfef5115073aeb08eb130fe40decc15551e73bbb2aa6ec77c0fc997e8fbf3477f
@@ -9,8 +9,10 @@ jobs:
9
9
  fail-fast: false
10
10
  matrix:
11
11
  os: [ ubuntu-latest, macos-latest ]
12
- # ruby: [ 2.5, 2.6, 2.7 ] # TODO: Wait for supporting Ruby 2.7 in google-protobuf
13
- ruby: [ 2.5, 2.6 ]
12
+ # TODO: Wait for supporting Ruby 3.0 in simplecov-cobertura.
13
+ # See https://github.com/dashingrocket/simplecov-cobertura/pull/16
14
+ # ruby: [ 2.5, 2.6, 2.7, 3.0 ]
15
+ ruby: [ 2.5, 2.6, 2.7 ]
14
16
  runs-on: ${{ matrix.os }}
15
17
 
16
18
  steps:
@@ -30,3 +32,9 @@ jobs:
30
32
  - run: bundle install --jobs 4 --retry 3
31
33
 
32
34
  - run: bundle exec rspec
35
+ env:
36
+ CI: true
37
+
38
+ - uses: codecov/codecov-action@v1
39
+ with:
40
+ file: ./coverage/coverage.xml
@@ -15,11 +15,9 @@ jobs:
15
15
  sudo apt-get update
16
16
  sudo apt-get install libsqlite3-dev
17
17
 
18
- - name: Setup reviewdog
19
- run: |
20
- mkdir -p $HOME/bin
21
- curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b $HOME/bin
22
- echo ::add-path::$HOME/bin
18
+ - uses: reviewdog/action-setup@v1
19
+ with:
20
+ reviewdog_version: latest
23
21
 
24
22
  - run: gem install bundler -v 2.1.4
25
23
 
@@ -28,6 +26,8 @@ jobs:
28
26
  - run: bundle install --jobs 4 --retry 3
29
27
 
30
28
  - name: Run Rubocop with Reviewdog
29
+ env:
30
+ REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31
31
  run: |
32
32
  bundle exec rubocop --fail-level E \
33
33
  | reviewdog -f=rubocop -reporter=github-pr-review
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ ## Unreleased
2
+
3
+ ## 0.5.0
4
+
5
+ - Bump `computed_model` from 0.2.2 to 0.3.0 https://github.com/wantedly/pb-serializer/pull/38
6
+
7
+ ## 0.4.0
8
+
9
+ - Make `#initialize` extensible used with `define_primary_loader` https://github.com/wantedly/pb-serializer/pull/31
10
+ - Supoprt `ignore` directive https://github.com/wantedly/pb-serializer/pull/36
11
+ - Support field mask https://github.com/wantedly/pb-serializer/pull/34
12
+
13
+ ## 0.3.0
14
+
15
+ - Support `if` option https://github.com/wantedly/pb-serializer/pull/24
16
+ - Improve error handling https://github.com/wantedly/pb-serializer/pull/26
17
+ - raise `MissingMessageTypeError` if `message` declaration is missed
18
+ - raise `MissingFieldError` if `attribute` declaration is missed
19
+ - raise `InvalidOptionError` when `attribute` receives invalid params
20
+ - Introduce Pb::Serializer.configure https://github.com/wantedly/pb-serializer/pull/27
21
+ - Add `missing_field_behavior` config to suppress `MissingFieldError`
22
+ - Rename `InvalidOptionError` -> `InvalidAttributeOptionError`
23
+ - Skip serializing when a value is already serialized https://github.com/wantedly/pb-serializer/pull/29
24
+
25
+
26
+ ## 0.2.1
27
+
28
+ - **BREAKING CHANGE** `required` -> `allow_nil` https://github.com/wantedly/pb-serializer/pull/21
29
+ - Make Serializer's constructors extensible https://github.com/wantedly/pb-serializer/pull/22
30
+
31
+ ## 0.2.0
32
+
33
+ - **BREAKING CHANGE** https://github.com/wantedly/pb-serializer/pull/17
34
+ - Support loading and serializing arrays
35
+ - Bump `computed_model` from 0.1.0 to 0.2.1
36
+ - Change API
37
+ - Add example specs https://github.com/wantedly/pb-serializer/pull/18
38
+
39
+
40
+ ## 0.1.0
41
+
42
+ Initial release.
data/README.md CHANGED
@@ -1,32 +1,41 @@
1
1
  # Pb::Serializer
2
+ [![CI](https://github.com/wantedly/pb-serializer/workflows/CI/badge.svg?branch=master)](https://github.com/wantedly/pb-serializer/actions?query=workflow%3ACI+branch%3Amaster)
3
+ [![codecov](https://codecov.io/gh/wantedly/pb-serializer/branch/master/graph/badge.svg)](https://codecov.io/gh/wantedly/pb-serializer)
4
+ [![Gem Version](https://badge.fury.io/rb/pb-serializer.svg)](https://badge.fury.io/rb/pb-serializer)
5
+ [![License](https://img.shields.io/github/license/wantedly/pb-serializer)](./LICENSE)
2
6
 
3
7
  ```rb
4
8
  class UserSerializer < Pb::Serializer::Base
5
9
  message YourApp::User
6
10
 
7
- attribute :id, required: true
8
- attribute :name, required: true
9
- attribute :posts, required: true, serializer: PostSerializer
11
+ attribute :id
12
+ attribute :name
13
+ attribute :posts
10
14
 
11
- define_loader :posts do |users, subdeps, **|
12
- posts = Post.where(user_id: users.map(&:id)).index_by(&:user_id)
13
- users.each do |user|
14
- user.posts = posts[user.id]
15
- end
15
+ define_primary_loader :user do |subdeps, ids:, **|
16
+ User.where(id: ids).preload(subdeps).map { |u| new(u) }
17
+ end
18
+
19
+ define_loader :posts, key: -> { id } do |user_ids, subdeps, **|
20
+ PostSerializer.bulk_load(user_id: user_ids, with: subdeps).group_by { |s| s.post.user_id }
16
21
  end
17
22
 
18
23
  dependency :posts
19
24
  computed def post_count
20
- object.posts.size
25
+ posts.count
21
26
  end
22
27
  end
23
28
 
24
29
  class PostSerializer < Pb::Serializer::Base
25
30
  message YourApp::Post
26
31
 
27
- attribute :id, required: true
28
- attribute :title, required: true
29
- attribute :body, required: true
32
+ define_primary_loader :post do |subdeps, user_ids:, **|
33
+ Post.where(user_id: user_ids).preload(subdeps).map { |p| new(p) }
34
+ end
35
+
36
+ attribute :id
37
+ attribute :title
38
+ attribute :body
30
39
  end
31
40
 
32
41
  class UserGrpcService < YourApp::UserService::Service
@@ -34,22 +43,23 @@ class UserGrpcService < YourApp::UserService::Service
34
43
  # @param call [GRPC::ActiveCall::SingleReqView]
35
44
  # @return [YourApp::User]
36
45
  def get_users(req, call)
37
- user = User.find(id: req.user_id)
38
- UserSerializer.serialize(user, with: req.field_mask)
46
+ UserSerializer.bulk_load_and_serialize(ids: [req.user_id], with: req.field_mask)[0]
39
47
  end
40
48
 
41
49
  # @param req [YourApp::ListFriendUsersRequest]
42
50
  # @param call [GRPC::ActiveCall::SingleReqView]
43
51
  # @return [YourApp::ListFriendUsersResponse]
44
52
  def list_friend_users(req, call)
45
- friends = User.find(current_user_id).friends
53
+ current_user = User.find(current_user_id)
46
54
  YourApp::ListFriendUsersResponse.new(
47
- users: UserSerializer.serialize_repeated(friends, with: req.field_mask),
55
+ users: UserSerializer.bulk_load_and_serialize(ids: current_user.friend_ids, with: req.field_mask)
48
56
  )
49
57
  end
50
58
  end
51
59
  ```
52
60
 
61
+ More examples are available under [./spec/examples](./spec/examples).
62
+
53
63
 
54
64
  ## Installation
55
65
 
data/codecov.yml ADDED
@@ -0,0 +1,6 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ target: 95%
6
+ patch: off
@@ -1,16 +1,21 @@
1
1
  module Pb
2
2
  module Serializable
3
+ extend ActiveSupport::Concern
4
+ include ComputedModel::Model
3
5
  def self.included(base)
4
6
  base.extend ClassMethods
5
- base.include ComputedModel
7
+ base.singleton_class.prepend Hook
6
8
  end
7
9
 
10
+ # @param with [
11
+ # Google::Protobuf::FieldMask,
12
+ # Array<(Symbol, Hash)>,
13
+ # Hash{Symbol=>(Array,Symbol,Hash)},
14
+ # Pb::Serializer::NormalizedMask
15
+ # ]
8
16
  def to_pb(with: nil)
9
- if with.nil?
10
- with = ::Pb::Serializer.build_default_mask(self.class.message_class.descriptor)
11
- end
12
-
13
- self.class.bulk_load_and_compute(Array(self), with)
17
+ with ||= ::Pb::Serializer.build_default_mask(self.class.message_class.descriptor)
18
+ with = ::Pb::Serializer::NormalizedMask.build(with)
14
19
 
15
20
  oneof_set = []
16
21
 
@@ -18,26 +23,38 @@ module Pb
18
23
  self.class.message_class.descriptor.each do |fd|
19
24
  attr = self.class.find_attribute_by_field_descriptor(fd)
20
25
 
21
- next unless attr # TODO
26
+ unless attr
27
+ msg = "#{self.class.message_class.name}.#{fd.name} is missed in #{self.class.name}"
22
28
 
23
- raise "#{self.name}.#{attr.name} is not defined" unless respond_to?(attr.name)
24
-
25
- v = public_send(attr.name)
26
- v = attr.convert_to_pb(v)
29
+ case Pb::Serializer.configuration.missing_field_behavior
30
+ when :raise then raise ::Pb::Serializer::MissingFieldError, msg
31
+ when :warn then Pb::Serializer.logger.warn msg
32
+ end
27
33
 
28
- if attr.required && attr.field_descriptor.default == v
29
- raise ::Pb::Serializer::ValidationError, "#{object.class.name}##{attr.name} is required"
34
+ next
30
35
  end
31
36
 
32
- next if v.nil?
37
+ next unless with.key?(attr.name)
38
+ next unless attr.serializable?(self)
39
+
40
+ raise "#{self.name}.#{attr.name} is not defined" unless respond_to?(attr.name)
41
+
42
+ v = public_send(attr.name)
43
+ v = attr.convert_to_pb(v, with: with[attr.name])
33
44
 
34
45
  if attr.oneof?
35
- if oneof_set.include?(attr.oneof)
36
- raise ::Pb::Serializer::ConflictOneofError, "#{object.class.name}##{attr.name} is oneof attribute"
46
+ if !v.nil?
47
+ if oneof_set.include?(attr.oneof)
48
+ raise ::Pb::Serializer::ConflictOneofError, "#{primary_object.class.name}##{attr.name} is oneof attribute"
49
+ end
50
+ oneof_set << attr.oneof
37
51
  end
38
- oneof_set << attr.oneof
52
+ elsif !attr.allow_nil? && v.nil?
53
+ raise ::Pb::Serializer::ValidationError, "#{primary_object.class.name}##{attr.name} is required"
39
54
  end
40
55
 
56
+ next if v.nil?
57
+
41
58
  if attr.repeated?
42
59
  o.public_send(attr.name).push(*v)
43
60
  else
@@ -47,30 +64,67 @@ module Pb
47
64
 
48
65
  self.class.oneofs.each do |oneof|
49
66
  next if oneof_set.include?(oneof.name)
50
- next unless oneof.required?
51
- raise ::Pb::Serializer::ValidationError, "#{object.class.name}##{oneof.name} is required"
67
+ next if oneof.allow_nil?
68
+ raise ::Pb::Serializer::ValidationError, "#{primary_object.class.name}##{oneof.name} is required"
52
69
  end
53
70
 
54
71
  o
55
72
  end
56
73
 
74
+ private def primary_object
75
+ primary_object_name = self.class.__pb_serializer_primary_model_name
76
+ if primary_object_name
77
+ send(primary_object_name)
78
+ elsif kind_of?(Serializer::Base)
79
+ send(:object)
80
+ else
81
+ self
82
+ end
83
+ end
84
+
85
+ module Hook
86
+ def define_primary_loader(name)
87
+ self.__pb_serializer_primary_model_name = name
88
+
89
+ super
90
+ end
91
+
92
+ def computed(name)
93
+ __pb_serializer_attrs << name
94
+
95
+ super
96
+ end
97
+
98
+ def define_loader(name, **)
99
+ __pb_serializer_attrs << name
100
+
101
+ super
102
+ end
103
+ end
104
+
57
105
  module ClassMethods
58
106
  attr_reader :message_class
107
+ attr_accessor :__pb_serializer_primary_model_name
108
+
59
109
  def message(klass)
60
110
  @message_class = klass
61
111
  end
62
112
 
63
113
  # @param name [Symbol] An attribute name
64
- # @param required [Boolean] Set true if this attribute should not zero-value
65
- # @param serializer [Class] A serializer class for this attribute
66
- def attribute(name, required: false, serializer: nil)
114
+ # @param [Hash] opts options
115
+ # @option opts [Boolean] :allow_nil Set true if this attribute allow to be nil
116
+ # @option opts [Class] :serializer A serializer class for this attribute
117
+ # @option opts [String, Symbol, Proc] :if A method, proc or string to call to determine to serialize this field
118
+ def attribute(name, opts = {})
119
+ raise ::Pb::Serializer::MissingMessageTypeError, "message specificaiton is missed" unless message_class
120
+
67
121
  fd = message_class.descriptor.find { |fd| fd.name.to_sym == name }
122
+
68
123
  raise ::Pb::Serializer::UnknownFieldError, "#{name} is not defined in #{message_class.name}" unless fd
69
124
 
70
125
  attr = ::Pb::Serializer::Attribute.new(
71
126
  name: name,
72
- required: required,
73
- serializer_class: serializer,
127
+ options: opts,
74
128
  field_descriptor: fd,
75
129
  oneof: @current_oneof&.name,
76
130
  )
@@ -79,28 +133,43 @@ module Pb
79
133
  @attr_by_name[name] = attr
80
134
 
81
135
  define_method attr.name do
82
- object.public_send(attr.name) # FIXME: This does not work without ::Pb::Serializer::Base
136
+ primary_object.public_send(attr.name)
83
137
  end
138
+ end
84
139
 
85
- dependency # FIXME
86
- computed attr.name
140
+ # @param names [Array<Symbol>] Attribute names to be ignored
141
+ def ignore(*names)
142
+ names.each do |name|
143
+ attribute name, ignore: true
144
+ end
87
145
  end
88
146
 
89
- # @param object [Object, Array]
90
- # @param message_class [Class]
91
- def serialize(object, with: nil)
92
- if self < ::Pb::Serializer::Base
93
- new(object).to_pb
94
- else
95
- object.to_pb
147
+ # @param with [Array, Hash, Google::Protobuf::FieldMask, nil]
148
+ # @return [Array]
149
+ def bulk_load_and_serialize(with: nil, **args)
150
+ bulk_load(with: with, **args).map { |s| s.to_pb(with: with) }
151
+ end
152
+
153
+ def bulk_load(with: nil, **args)
154
+ with ||= ::Pb::Serializer.build_default_mask(message_class.descriptor)
155
+ with = ::Pb::Serializer::NormalizedMask.build(with)
156
+ with = with.reject { |c| (__pb_serializer_attrs & (c.kind_of?(Hash) ? c.keys : [c])).empty? }
157
+
158
+ primary_object_name = __pb_serializer_primary_model_name
159
+ if primary_object_name
160
+ (with[primary_object_name] ||= []) << true
161
+ elsif self < Serializer::Base
162
+ (with[:object] ||= []) << true
96
163
  end
164
+
165
+ bulk_load_and_compute(with, **args)
97
166
  end
98
167
 
99
- def oneof(name, required: true)
168
+ def oneof(name, allow_nil: false)
100
169
  @oneof_by_name ||= {}
101
170
  @current_oneof = ::Pb::Serializer::Oneof.new(
102
171
  name: name,
103
- required: required,
172
+ allow_nil: allow_nil,
104
173
  attributes: [],
105
174
  )
106
175
  yield
@@ -108,10 +177,14 @@ module Pb
108
177
  @current_oneof = nil
109
178
  end
110
179
 
180
+ private def __pb_serializer_attrs
181
+ @__pb_serializer_attrs ||= Set.new
182
+ end
183
+
111
184
  # @param fd [Google::Protobuf::FieldDescriptor] a field descriptor
112
185
  # @return [Pb::Serializer::Attribute, nil]
113
186
  def find_attribute_by_field_descriptor(fd)
114
- @attr_by_name[fd.name.to_sym]
187
+ (@attr_by_name || {})[fd.name.to_sym]
115
188
  end
116
189
 
117
190
  def oneofs
data/lib/pb/serializer.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require "pb/serializer/version"
2
2
  require "the_pb"
3
3
  require "computed_model"
4
+ require "google/protobuf/field_mask_pb"
4
5
 
5
6
  require "pb/serializable"
7
+ require "pb/serializer/normalized_mask"
6
8
  require "pb/serializer/base"
7
9
  require "pb/serializer/attribute"
8
10
  require "pb/serializer/oneof"
@@ -10,11 +12,58 @@ require "pb/serializer/oneof"
10
12
  module Pb
11
13
  module Serializer
12
14
  class Error < StandardError; end
15
+ class InvalidConfigurationError < Error; end
16
+ class MissingMessageTypeError < Error; end
13
17
  class UnknownFieldError < Error; end
14
18
  class ValidationError < Error; end
15
19
  class ConflictOneofError < Error; end
20
+ class InvalidAttributeOptionError < Error; end
21
+ class MissingFieldError < Error; end
22
+
23
+ class Configuration
24
+ # @!attribute logger
25
+ # @return [Logger]
26
+ attr_accessor :logger
27
+ # @!attribute [r] missing_field_behavior
28
+ # @return [:raise, :warn, :ignore] default: `:raise`
29
+ attr_reader :missing_field_behavior
30
+
31
+ def initialize
32
+ self.missing_field_behavior = :raise
33
+ self.logger = Logger.new(STDOUT)
34
+ end
35
+
36
+ # @param v [:raise, :warn, :ignore]
37
+ def missing_field_behavior=(v)
38
+ @missing_field_behavior = v
39
+
40
+ unless %i(raise warn ignore).include?(v)
41
+ raise InvalidConfigurationError, "missing_field_behavior #{v} is not allowed"
42
+ end
43
+ end
44
+ end
16
45
 
17
46
  class << self
47
+ # @example
48
+ # Pb::Serializer.configuration do |c|
49
+ # c.missing_field_behavior = :raise # :raise, :warn or :ignore (defualt: :raise)
50
+ # end
51
+ # @yield [c]
52
+ # @yieldparam [Configuration] config
53
+ def configure
54
+ yield configuration
55
+ end
56
+
57
+ # @return [Pb::Serializer::Configuration]
58
+ def configuration
59
+ @configuraiton ||= Configuration.new
60
+ end
61
+
62
+ # @return [Logger]
63
+ def logger
64
+ configuration.logger
65
+ end
66
+
18
67
  # @param [Google::Protobuf::Descriptor]
19
68
  def build_default_mask(descriptor)
20
69
  set =
@@ -2,16 +2,31 @@ module Pb
2
2
  module Serializer
3
3
  class Attribute < Struct.new(
4
4
  :name,
5
- :required,
6
- :serializer_class,
5
+ :options,
7
6
  :field_descriptor,
8
7
  :oneof,
9
8
  keyword_init: true,
10
9
  )
11
10
 
11
+ ALLOWED_OPTIONS = Set[:allow_nil, :if, :serializer, :ignore].freeze
12
+
13
+ def initialize(options:, **)
14
+ super
15
+
16
+ unknown_options = options.keys.to_set - ALLOWED_OPTIONS
17
+ unless unknown_options.empty?
18
+ raise InvalidAttributeOptionError, "unknown options are specified in #{name} attribute: #{unknown_options.to_a}"
19
+ end
20
+ end
21
+
12
22
  # @return [Boolean]
13
- def required?
14
- required
23
+ def allow_nil?
24
+ options.fetch(:allow_nil, false)
25
+ end
26
+
27
+ # @return [Class]
28
+ def serializer_class
29
+ options[:serializer]
15
30
  end
16
31
 
17
32
  # @return [Boolean]
@@ -19,16 +34,37 @@ module Pb
19
34
  field_descriptor.label == :repeated
20
35
  end
21
36
 
37
+ # @return [Boolean]
38
+ def serializable?(s)
39
+ return false if options[:ignore]
40
+
41
+ cond = options[:if]
42
+
43
+ return true unless cond
44
+
45
+ case cond
46
+ when String, Symbol; then s.send(cond)
47
+ when Proc; then s.instance_exec(&cond)
48
+ else raise InvalidAttributeOptionError, "`if` option can accept only Symbol, String or Proc. but got #{cond.class}"
49
+ end
50
+ end
51
+
22
52
  def oneof?
23
53
  !oneof.nil?
24
54
  end
25
55
 
26
56
  # @param v [Object]
27
- def convert_to_pb(v, should_repeat: repeated?)
28
- return v.map { |i| convert_to_pb(i, should_repeat: false) } if should_repeat
57
+ # @param with [Pb::Serializer::NormalizedMask]
58
+ def convert_to_pb(v, with: nil, should_repeat: repeated?)
59
+ return nil if v.nil?
60
+ return v.map { |i| convert_to_pb(i, should_repeat: false, with: with) } if should_repeat
29
61
 
30
62
  case field_descriptor.type
31
63
  when :message
64
+ if v.class < Google::Protobuf::MessageExts && v.class.descriptor.name == field_descriptor.submsg_name
65
+ return v
66
+ end
67
+
32
68
  case field_descriptor.submsg_name
33
69
  when "google.protobuf.Timestamp" then Pb.to_timestamp(v)
34
70
  when "google.protobuf.StringValue" then Pb.to_strval(v)
@@ -41,9 +77,8 @@ module Pb
41
77
  when "google.protobuf.BoolValue" then Pb.to_boolval(v)
42
78
  when "google.protobuf.BytesValue" then Pb.to_bytesval(v)
43
79
  else
44
- return nil if v.nil?
45
- return serializer_class.serialize(v) if serializer_class
46
- return v.to_pb if v.kind_of?(::Pb::Serializable)
80
+ return serializer_class.new(v).to_pb(with: with) if serializer_class
81
+ return v.to_pb(with: with) if v.kind_of?(::Pb::Serializable)
47
82
 
48
83
  raise "serializer was not found for #{field_descriptor.submsg_name}"
49
84
  end
@@ -3,13 +3,31 @@ module Pb
3
3
  class Base
4
4
  def self.inherited(base)
5
5
  base.include ::Pb::Serializable
6
+ base.singleton_class.prepend Hook
6
7
  end
7
8
 
8
9
  attr_reader :object
9
10
 
10
- def initialize(object)
11
+ def initialize(object, *)
11
12
  @object = object
12
13
  end
14
+
15
+ module Hook
16
+ def define_primary_loader(name, &block)
17
+ class_eval <<~RUBY
18
+ module PbSerializerDefinePrimaryLoaderPrependMethods
19
+ def initialize(*args)
20
+ super
21
+ @#{name} = object
22
+ end
23
+ end
24
+
25
+ prepend PbSerializerDefinePrimaryLoaderPrependMethods
26
+ RUBY
27
+
28
+ super
29
+ end
30
+ end
13
31
  end
14
32
  end
15
33
  end
@@ -0,0 +1,57 @@
1
+ module Pb::Serializer
2
+ class NormalizedMask < ::Hash
3
+ class << self
4
+ # @param [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash)}]
5
+ # @return [Hash{Symbol=>Hash}]
6
+ def build(input)
7
+ return input if input.kind_of? self
8
+
9
+ normalized = new
10
+
11
+ case input
12
+ when Google::Protobuf::FieldMask
13
+ normalized = normalize_mask_paths(input.paths)
14
+ when Array
15
+ input.each do |v|
16
+ deep_merge!(normalized, build(v))
17
+ end
18
+ when Hash
19
+ input.each do |k, v|
20
+ normalized[k] ||= new
21
+ deep_merge!(normalized[k], build(v))
22
+ end
23
+ when Symbol
24
+ normalized[input] ||= new
25
+ else
26
+ raise "not supported field mask type: #{input.class}"
27
+ end
28
+
29
+ normalized
30
+ end
31
+
32
+ private
33
+
34
+ # @param [Array<String>]
35
+ # @return [Hash{Symbol=>Hash}]
36
+ def normalize_mask_paths(paths)
37
+ paths_by_key = {}
38
+
39
+ paths.each do |path|
40
+ key, rest = path.split('.', 2)
41
+ paths_by_key[key.to_sym] ||= []
42
+ paths_by_key[key.to_sym].push(rest) if rest && !rest.empty?
43
+ end
44
+
45
+ paths_by_key.keys.each_with_object(new) do |key, normalized|
46
+ normalized[key] = normalize_mask_paths(paths_by_key[key])
47
+ end
48
+ end
49
+
50
+ def deep_merge!(h1, h2)
51
+ h1.merge!(h2) do |_k, v1, v2|
52
+ deep_merge!(v1, v2)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,13 +2,13 @@ module Pb
2
2
  module Serializer
3
3
  class Oneof < Struct.new(
4
4
  :name,
5
- :required,
5
+ :allow_nil,
6
6
  :attributes,
7
7
  keyword_init: true,
8
8
  )
9
9
  # @return [Boolean]
10
- def required?
11
- required
10
+ def allow_nil?
11
+ allow_nil
12
12
  end
13
13
  end
14
14
  end
@@ -1,5 +1,5 @@
1
1
  module Pb
2
2
  module Serializer
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.5.0".freeze
4
4
  end
5
5
  end
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  rails_versions = [">= 5.2", "< 6.1"]
30
30
  spec.add_runtime_dependency "google-protobuf", "~> 3.0"
31
31
  spec.add_runtime_dependency "the_pb", "~> 0.0.1"
32
- spec.add_runtime_dependency "computed_model", "~> 0.1.0"
32
+ spec.add_runtime_dependency "computed_model", "~> 0.3.0"
33
33
 
34
34
  spec.add_development_dependency "activerecord", rails_versions
35
35
  spec.add_development_dependency "bundler", "~> 2.0"
@@ -38,4 +38,6 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "rspec", "~> 3.0"
39
39
  spec.add_development_dependency "rubocop", "0.67.2" # for onkcop
40
40
  spec.add_development_dependency "sqlite3", "~> 1.4"
41
+ spec.add_development_dependency "simplecov", "~> 0.18.5"
42
+ spec.add_development_dependency "simplecov-cobertura", "~> 1.3"
41
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pb-serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - izumin5210
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-17 00:00:00.000000000 Z
11
+ date: 2021-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.1.0
47
+ version: 0.3.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.1.0
54
+ version: 0.3.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activerecord
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -156,6 +156,34 @@ dependencies:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
158
  version: '1.4'
159
+ - !ruby/object:Gem::Dependency
160
+ name: simplecov
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: 0.18.5
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: 0.18.5
173
+ - !ruby/object:Gem::Dependency
174
+ name: simplecov-cobertura
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '1.3'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '1.3'
159
187
  description: Serialize objects into Protocol Buffers messages
160
188
  email:
161
189
  - m@izum.in
@@ -169,6 +197,7 @@ files:
169
197
  - ".rspec"
170
198
  - ".rubocop.yml"
171
199
  - ".rubocop_todo.yml"
200
+ - CHANGELOG.md
172
201
  - CODE_OF_CONDUCT.md
173
202
  - Gemfile
174
203
  - LICENSE.txt
@@ -176,10 +205,12 @@ files:
176
205
  - Rakefile
177
206
  - bin/console
178
207
  - bin/setup
208
+ - codecov.yml
179
209
  - lib/pb/serializable.rb
180
210
  - lib/pb/serializer.rb
181
211
  - lib/pb/serializer/attribute.rb
182
212
  - lib/pb/serializer/base.rb
213
+ - lib/pb/serializer/normalized_mask.rb
183
214
  - lib/pb/serializer/oneof.rb
184
215
  - lib/pb/serializer/version.rb
185
216
  - pb-serializer.gemspec
@@ -190,7 +221,7 @@ metadata:
190
221
  homepage_uri: https://github.com/izumin5210/pb-serializer
191
222
  source_code_uri: https://github.com/izumin5210/pb-serializer
192
223
  changelog_uri: https://github.com/izumin5210/pb-serializer
193
- post_install_message:
224
+ post_install_message:
194
225
  rdoc_options: []
195
226
  require_paths:
196
227
  - lib
@@ -205,8 +236,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
236
  - !ruby/object:Gem::Version
206
237
  version: '0'
207
238
  requirements: []
208
- rubygems_version: 3.0.3
209
- signing_key:
239
+ rubygems_version: 3.2.3
240
+ signing_key:
210
241
  specification_version: 4
211
242
  summary: Serialize objects into Protocol Buffers messages
212
243
  test_files: []