bright_serializer 0.3.1 → 0.4.1

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: 193184e7a6c008dc18745e187bbf375a9c42a6568d0730ed39dcea3c04888115
4
- data.tar.gz: e574c34963493d24af6039f534f4074e398d0515f30d6d4ae716c0e8ffbbad33
3
+ metadata.gz: 840379a67e4354592d042785306facc35b8e663ed8cc27da09b9158b3d43fc6f
4
+ data.tar.gz: 817cf7a8dca2434c8e89cf3813678c1005086d9edb0fd11c76c4a3461470873f
5
5
  SHA512:
6
- metadata.gz: 4271988147d4f68f39a1846d7cbcbdba6cbc445e2ee28c58160f8a124658cd6cebd4bebf5e0c5e4f56c811229ccb295c44e914ddeaba4c0289fe232c12cf0b59
7
- data.tar.gz: ee98dfaf4e70a123bfe5ea46dc0817e0beaefb93238f21ed856da0eb5e722a864c9bc42256611ec81bdd6722d3f751afb9e1b8d1a42b8219c7a77c3fbabe1180
6
+ metadata.gz: 1a598fcf576edf3395723c01dbaa7c00c45bc76ad2363599b9e16af0a7f4662a55007a6de49073cc2bb2bf36ceb63320c3fcfd58459147917b041deda1ae3204
7
+ data.tar.gz: 711ca41015c6fed810bd22ae1ce1914d87f9b40fdb33b084b891b4134ea5af1d291a1bce95e608f9f8cd3babf25440f1cce05433c6a8ddd935a4b2c43eebc907
data/.rubocop.yml CHANGED
@@ -4,6 +4,8 @@ require:
4
4
 
5
5
  AllCops:
6
6
  NewCops: enable
7
+ DisplayStyleGuide: true # Include styleguide and reference URLs
8
+ ExtraDetails: true # Include cop details
7
9
 
8
10
  Metrics/BlockLength:
9
11
  Enabled: false
@@ -35,5 +37,8 @@ Metrics/CyclomaticComplexity:
35
37
  Metrics/AbcSize:
36
38
  Enabled: false
37
39
 
40
+ Metrics/ParameterLists:
41
+ Max: 6
42
+
38
43
  RSpec/NestedGroups:
39
44
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ ## 0.4.1 (2022-04-10)
6
+
7
+ * Add deprecation warning for serialize nil object. See [issue #103](https://github.com/petalmd/bright_serializer/issues/103). ([v0.4.0...v0.4.1](https://github.com/petalmd/bright_serializer/compare/v0.4.0...v0.4.1))
8
+
9
+ ## 0.4.0 (2022-11-10)
10
+
11
+ * Added relation helper methods `has_one`, `has_many`, `belongs_to` ([#49](https://github.com/petalmd/bright_serializer/pull/49))
12
+ * Performance improvements, save in instance attributes to serialize. ([#100](https://github.com/petalmd/bright_serializer/pull/100))
13
+ * Performance improvements, calculate attributes to serialize only once. ([#98](https://github.com/petalmd/bright_serializer/pull/98))
14
+ * Add instrumentation. ([#90](https://github.com/petalmd/bright_serializer/pull/90))
15
+
5
16
  ## 0.3.1 (2022-09-28)
6
17
 
7
18
  * Performance improvements, use nil instead of empty set. ([#97](https://github.com/petalmd/bright_serializer/pull/97))
data/Gemfile.lock CHANGED
@@ -1,14 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bright_serializer (0.3.1)
4
+ bright_serializer (0.4.1)
5
+ activesupport (>= 5.2)
5
6
  oj (~> 3)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ activesupport (5.2.8.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 0.7, < 2)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
10
16
  ast (2.4.1)
11
- concurrent-ruby (1.1.7)
17
+ concurrent-ruby (1.1.10)
12
18
  coveralls_reborn (0.22.0)
13
19
  simplecov (>= 0.18.1, < 0.22.0)
14
20
  term-ansicolor (~> 1.6)
@@ -18,8 +24,9 @@ GEM
18
24
  docile (1.4.0)
19
25
  faker (2.15.1)
20
26
  i18n (>= 1.6, < 2)
21
- i18n (1.8.5)
27
+ i18n (1.12.0)
22
28
  concurrent-ruby (~> 1.0)
29
+ minitest (5.16.3)
23
30
  oj (3.11.1)
24
31
  parallel (1.20.1)
25
32
  parser (3.0.0.0)
@@ -69,8 +76,11 @@ GEM
69
76
  term-ansicolor (1.7.1)
70
77
  tins (~> 1.0)
71
78
  thor (1.1.0)
79
+ thread_safe (0.3.6)
72
80
  tins (1.29.1)
73
81
  sync
82
+ tzinfo (1.2.10)
83
+ thread_safe (~> 0.1)
74
84
  unicode-display_width (1.7.0)
75
85
 
76
86
  PLATFORMS
data/README.md CHANGED
@@ -74,7 +74,7 @@ class AccountSerializer
74
74
  include BrightSerializer::Serializer
75
75
  attributes :id, :first_name, :last_name
76
76
 
77
- attribute :email, if: -> { |object, params| params[:current_user].is_admin? }
77
+ attribute :email, if: proc { |object, params| params[:current_user].is_admin? }
78
78
  end
79
79
  ```
80
80
 
@@ -108,7 +108,18 @@ AccountSerializer.new(Account.first, fields: [:first_name, :last_name]).to_json
108
108
 
109
109
  ### Relations
110
110
 
111
- For now, relations or declared like any other attribute.
111
+ `has_one`, `has_many` and `belongs_to` helper methods can be use to use an other
112
+ serializer for nested attributes and relations.
113
+
114
+ * The `serializer` option must be provided.
115
+
116
+ When using theses methods you can pass options that will be apply like any other attributes.
117
+
118
+ * The option `if` can be pass to show or hide the relation.
119
+ * The option `entity` to generate API documentation.
120
+ * The option `fields` to only serializer some attributes of the nested object.
121
+ * The option `params` can be passed, it will be merged with the parent params.
122
+ * A block can be passed and the return value will be serialized with the `serializer` passed.
112
123
 
113
124
  ```ruby
114
125
  class FriendSerializer
@@ -120,12 +131,29 @@ class AccountSerializer
120
131
  include BrightSerializer::Serializer
121
132
  attributes :id, :first_name, :last_name
122
133
 
123
- attribute :friends do |object|
124
- FriendSerializer.new(object.friends)
125
- end
134
+ has_many :friends, serializer: 'FriendSerializer'
126
135
  end
127
136
  ```
128
137
 
138
+ ```ruby
139
+ # Block
140
+ has_one :best_friend, serializer: 'FriendSerializer' do |object, params|
141
+ # ...
142
+ end
143
+
144
+ # If
145
+ belongs_to :best_friend_of, serializer: 'FriendSerializer', if: proc { |object, params| '...' }
146
+
147
+ # Fields
148
+ has_one :best_friend, serializer: 'FriendSerializer', fields: [:first_name, :last_name]
149
+
150
+ # Params
151
+ has_one :best_friend, serializer: 'FriendSerializer', params: { static_param: true }
152
+
153
+ # Entity
154
+ has_one :best_friend, serializer: 'FriendSerializer', entity: { description: '...' }
155
+ ```
156
+
129
157
  ### Entity
130
158
 
131
159
  You can define the entity of your serializer to generate documentation with the option `entity`.
@@ -138,21 +166,26 @@ class AccountSerializer
138
166
  attribute :id, entity: { type: :string, description: 'The id of the account' }
139
167
  attribute :name
140
168
 
141
- attribute :friends,
169
+ has_many :friends, serializer: 'FriendSerializer',
142
170
  entity: {
143
- type: :array, items: { ref: 'FriendSerializer' }, description: 'The list the account friends.'
144
- } do |object|
145
- FriendSerializer.new(object.friends)
146
- end
171
+ type: :array, description: 'The list the account friends.'
172
+ }
147
173
  end
148
-
149
174
  ```
175
+
150
176
  Callable values are supported.
151
177
 
152
178
  ```ruby
153
179
  { entity: { type: :string, enum: -> { SomeModel::ENUMVALUES } } }
154
180
  ```
155
181
 
182
+ For relations only `type` need to be defined, `ref` will use the same class has `serializer`.
183
+
184
+ ```ruby
185
+ has_many :friends, serializer: 'FriendSerializer', entity: { type: :array }
186
+ has_one :best_friend, serializer: 'FriendSerializer', entity: { type: :object }
187
+ ```
188
+
156
189
  ### Instance
157
190
 
158
191
  If you have defined instance methods inside your serializer you can access them inside block attribute.
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.description = 'BrightSerializer is a minimalist implementation serializer for Ruby objects.'
15
15
  spec.homepage = 'https://github.com/petalmd/bright_serializer'
16
16
  spec.license = 'MIT'
17
- spec.required_ruby_version = '>= 2.5'
17
+ spec.required_ruby_version = '>= 2.6'
18
18
 
19
19
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
20
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.require_paths = ['lib']
38
38
 
39
39
  spec.add_runtime_dependency 'oj', '~> 3'
40
+ spec.add_dependency 'activesupport', '>= 5.2'
40
41
  spec.add_development_dependency 'bundler', '~> 2'
41
42
  spec.add_development_dependency 'faker', '~> 2'
42
43
  spec.add_development_dependency 'rake', '~> 13.0'
@@ -17,18 +17,7 @@ module BrightSerializer
17
17
  def serialize(serializer_instance, object, params)
18
18
  return unless object
19
19
 
20
- value =
21
- if @block
22
- if @block.arity.abs == 1
23
- serializer_instance.instance_exec(object, &@block)
24
- else
25
- serializer_instance.instance_exec(object, params, &@block)
26
- end
27
- elsif object.is_a?(Hash)
28
- object.key?(key) ? object[key] : object[key.to_s]
29
- else
30
- object.send(key)
31
- end
20
+ value = attribute_value(serializer_instance, object, params)
32
21
 
33
22
  value.respond_to?(:serializable_hash) ? value.serializable_hash : value
34
23
  end
@@ -38,5 +27,21 @@ module BrightSerializer
38
27
 
39
28
  @condition.call(object, params)
40
29
  end
30
+
31
+ private
32
+
33
+ def attribute_value(serializer_instance, object, params)
34
+ if @block
35
+ if @block.arity.abs == 1
36
+ serializer_instance.instance_exec(object, &@block)
37
+ else
38
+ serializer_instance.instance_exec(object, params, &@block)
39
+ end
40
+ elsif object.is_a?(Hash)
41
+ object.key?(key) ? object[key] : object[key.to_s]
42
+ else
43
+ object.public_send(key)
44
+ end
45
+ end
41
46
  end
42
47
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrightSerializer
4
+ class AttributeRelation < Attribute
5
+ def initialize(key, serializer, condition, entity, options, &block)
6
+ @serializer = serializer
7
+ @options = options || {}
8
+
9
+ add_entity_ref!(entity)
10
+ super(key, condition, entity, &block)
11
+ end
12
+
13
+ def serialize(serializer_instance, object, params)
14
+ return unless object
15
+
16
+ merged_params = nil
17
+ merged_params = (params || {}).merge(@options[:params] || {}) if params || @options[:params]
18
+ value = attribute_value(serializer_instance, object, merged_params)
19
+
20
+ class_serializer.new(value, params: merged_params, **@options).serializable_hash
21
+ end
22
+
23
+ private
24
+
25
+ def class_serializer
26
+ @class_serializer ||= @serializer.is_a?(String) ? Inflector.constantize(@serializer) : @serializer
27
+ end
28
+
29
+ def add_entity_ref!(entity)
30
+ return unless entity
31
+
32
+ if entity[:type].to_sym == :object && entity[:ref].nil?
33
+ entity[:ref] = @serializer
34
+ elsif entity[:type].to_sym == :array && entity.dig(:items, :ref).nil?
35
+ entity[:items] ||= {}
36
+ entity[:items][:ref] = @serializer
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrightSerializer
4
+ module Extensions
5
+ module Instrumentation
6
+ SERIALIZABLE_HASH_NOTIFICATION = 'render.bright_serializer.serializable_hash'
7
+ SERIALIZED_JSON_NOTIFICATION = 'render.bright_serializer.serializable_json'
8
+
9
+ def serializable_hash
10
+ ActiveSupport::Notifications.instrument(SERIALIZABLE_HASH_NOTIFICATION, serializer: self.class.name) do
11
+ super
12
+ end
13
+ end
14
+
15
+ alias to_hash serializable_hash
16
+
17
+ def serializable_json(*_args)
18
+ ActiveSupport::Notifications.instrument(SERIALIZED_JSON_NOTIFICATION, serializer: self.class.name) do
19
+ super
20
+ end
21
+ end
22
+
23
+ alias to_json serializable_json
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrightSerializer
4
+ module Extensions
5
+ def self.included(base)
6
+ instrumentation_extension(base)
7
+ end
8
+
9
+ def self.instrumentation_extension(base)
10
+ return unless defined? ActiveSupport
11
+
12
+ require_relative 'extensions/instrumentation'
13
+ base.prepend Instrumentation
14
+ end
15
+ end
16
+ end
@@ -1,15 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'oj'
4
- require 'set'
4
+ require 'active_support/deprecation'
5
5
  require_relative 'attribute'
6
+ require_relative 'attribute_relation'
6
7
  require_relative 'inflector'
7
8
  require_relative 'entity/base'
9
+ require_relative 'extensions'
8
10
 
9
11
  module BrightSerializer
10
12
  module Serializer
13
+ include Extensions
14
+
11
15
  SUPPORTED_TRANSFORMATION = %i[camel camel_lower dash underscore].freeze
12
16
  DEFAULT_OJ_OPTIONS = { mode: :compat, time_format: :ruby, use_to_json: true }.freeze
17
+ DEPRECATION_MESSAGE = 'BrightSerializer: Serializing `nil` will stop returning ' \
18
+ "a JSON with all attributes and null values.\n" \
19
+ "To keep the old behaviour use an empty hash `MySerializer.new(object || { }).to_json`.\n" \
20
+ "See: https://github.com/petalmd/bright_serializer/issues/103 for more details about this change.\n"
21
+ private_constant :DEPRECATION_MESSAGE
13
22
 
14
23
  def self.included(base)
15
24
  super
@@ -20,14 +29,13 @@ module BrightSerializer
20
29
  def initialize(object, **options)
21
30
  @object = object
22
31
  @params = options.delete(:params)
23
-
24
- fields = options.delete(:fields)
25
- @fields = fields ? Set.new(fields) : nil
32
+ @fields = options.delete(:fields)
26
33
  end
27
34
 
28
- def serialize(object)
29
- self.class.attributes_to_serialize.each_with_object({}) do |attribute, result|
30
- next if !@fields.nil? && !@fields.include?(attribute.key)
35
+ def serialize(object, attributes_to_serialize)
36
+ ActiveSupport::Deprecation.warn(DEPRECATION_MESSAGE) if object.nil?
37
+
38
+ attributes_to_serialize.each_with_object({}) do |attribute, result|
31
39
  next unless attribute.condition?(object, @params)
32
40
 
33
41
  result[attribute.transformed_key] = attribute.serialize(self, object, @params)
@@ -36,9 +44,9 @@ module BrightSerializer
36
44
 
37
45
  def serializable_hash
38
46
  if @object.respond_to?(:each) && !@object.respond_to?(:each_pair)
39
- @object.map { |o| serialize o }
47
+ @object.map { |o| serialize(o, instance_attributes_to_serialize) }
40
48
  else
41
- serialize(@object)
49
+ serialize(@object, instance_attributes_to_serialize)
42
50
  end
43
51
  end
44
52
 
@@ -70,9 +78,20 @@ module BrightSerializer
70
78
 
71
79
  alias attribute attributes
72
80
 
81
+ def has_one(key, serializer:, **options, &block) # rubocop:disable Naming/PredicateName
82
+ attribute = AttributeRelation.new(
83
+ key, serializer, options.delete(:if), options.delete(:entity), options, &block
84
+ )
85
+ attribute.transformed_key = run_transform_key(key)
86
+ @attributes_to_serialize << attribute
87
+ end
88
+
89
+ alias has_many has_one
90
+ alias belongs_to has_one
91
+
73
92
  def set_key_transform(transform_name) # rubocop:disable Naming/AccessorMethodName
74
93
  unless SUPPORTED_TRANSFORMATION.include?(transform_name)
75
- raise ArgumentError "Invalid transformation: #{SUPPORTED_TRANSFORMATION}"
94
+ raise ArgumentError, "Invalid transformation: #{SUPPORTED_TRANSFORMATION}"
76
95
  end
77
96
 
78
97
  @transform_method = transform_name
@@ -100,5 +119,18 @@ module BrightSerializer
100
119
  name.split('::').last.downcase
101
120
  end
102
121
  end
122
+
123
+ private
124
+
125
+ def instance_attributes_to_serialize
126
+ @instance_attributes_to_serialize ||=
127
+ if @fields.nil?
128
+ self.class.attributes_to_serialize
129
+ else
130
+ self.class.attributes_to_serialize.select do |field|
131
+ @fields.include?(field.key)
132
+ end
133
+ end
134
+ end
103
135
  end
104
136
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BrightSerializer
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bright_serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Francis Bastien
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-28 00:00:00.000000000 Z
11
+ date: 2023-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '5.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '5.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -100,8 +114,11 @@ files:
100
114
  - bright_serializer.gemspec
101
115
  - lib/bright_serializer.rb
102
116
  - lib/bright_serializer/attribute.rb
117
+ - lib/bright_serializer/attribute_relation.rb
103
118
  - lib/bright_serializer/entity/base.rb
104
119
  - lib/bright_serializer/entity/parser.rb
120
+ - lib/bright_serializer/extensions.rb
121
+ - lib/bright_serializer/extensions/instrumentation.rb
105
122
  - lib/bright_serializer/inflector.rb
106
123
  - lib/bright_serializer/serializer.rb
107
124
  - lib/bright_serializer/version.rb
@@ -120,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
137
  requirements:
121
138
  - - ">="
122
139
  - !ruby/object:Gem::Version
123
- version: '2.5'
140
+ version: '2.6'
124
141
  required_rubygems_version: !ruby/object:Gem::Requirement
125
142
  requirements:
126
143
  - - ">="