pb-serializer 0.1.0 → 0.5.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: 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: []