bright_serializer 0.3.1 → 0.4.1

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: 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
  - - ">="