bright_serializer 0.3.1 → 0.4.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: 193184e7a6c008dc18745e187bbf375a9c42a6568d0730ed39dcea3c04888115
4
- data.tar.gz: e574c34963493d24af6039f534f4074e398d0515f30d6d4ae716c0e8ffbbad33
3
+ metadata.gz: 948f2e438a96ccd17416ace3e91375fae18b749795937f6db2494e3f3358ab8a
4
+ data.tar.gz: 6541ab4c808bb8a872fcc02dfb8dc5747bda9a6344dc50594ad0dec5222ed676
5
5
  SHA512:
6
- metadata.gz: 4271988147d4f68f39a1846d7cbcbdba6cbc445e2ee28c58160f8a124658cd6cebd4bebf5e0c5e4f56c811229ccb295c44e914ddeaba4c0289fe232c12cf0b59
7
- data.tar.gz: ee98dfaf4e70a123bfe5ea46dc0817e0beaefb93238f21ed856da0eb5e722a864c9bc42256611ec81bdd6722d3f751afb9e1b8d1a42b8219c7a77c3fbabe1180
6
+ metadata.gz: 90cb6ed0a7eb05ca181a52a4597693382b0976ecf5753f856fa0b81eadb71be3b4ac7e7da3a41df7a18b1ef884b42007dae9c0daf6711d73686ad6c5c230b962
7
+ data.tar.gz: bf93c9d4b769e9f754b238d1efb297560647a1899679d2180af8ad16d0d554c80a124c056b9659481ecae6e326facf1922ccedff2dd5d7f5dd7d191c3828c8ed
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,13 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ ## 0.4.0 (2022-11-10)
6
+
7
+ * Added relation helper methods `has_one`, `has_many`, `belongs_to` ([#49](https://github.com/petalmd/bright_serializer/pull/49))
8
+ * Performance improvements, save in instance attributes to serialize. ([#100](https://github.com/petalmd/bright_serializer/pull/100))
9
+ * Performance improvements, calculate attributes to serialize only once. ([#98](https://github.com/petalmd/bright_serializer/pull/98))
10
+ * Add instrumentation. ([#90](https://github.com/petalmd/bright_serializer/pull/90))
11
+
5
12
  ## 0.3.1 (2022-09-28)
6
13
 
7
14
  * Performance improvements, use nil instead of empty set. ([#97](https://github.com/petalmd/bright_serializer/pull/97))
data/Gemfile.lock CHANGED
@@ -1,14 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bright_serializer (0.3.1)
4
+ bright_serializer (0.4.0)
5
5
  oj (~> 3)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ activesupport (5.2.8.1)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 0.7, < 2)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
10
15
  ast (2.4.1)
11
- concurrent-ruby (1.1.7)
16
+ concurrent-ruby (1.1.10)
12
17
  coveralls_reborn (0.22.0)
13
18
  simplecov (>= 0.18.1, < 0.22.0)
14
19
  term-ansicolor (~> 1.6)
@@ -18,8 +23,9 @@ GEM
18
23
  docile (1.4.0)
19
24
  faker (2.15.1)
20
25
  i18n (>= 1.6, < 2)
21
- i18n (1.8.5)
26
+ i18n (1.12.0)
22
27
  concurrent-ruby (~> 1.0)
28
+ minitest (5.16.3)
23
29
  oj (3.11.1)
24
30
  parallel (1.20.1)
25
31
  parser (3.0.0.0)
@@ -69,14 +75,18 @@ GEM
69
75
  term-ansicolor (1.7.1)
70
76
  tins (~> 1.0)
71
77
  thor (1.1.0)
78
+ thread_safe (0.3.6)
72
79
  tins (1.29.1)
73
80
  sync
81
+ tzinfo (1.2.10)
82
+ thread_safe (~> 0.1)
74
83
  unicode-display_width (1.7.0)
75
84
 
76
85
  PLATFORMS
77
86
  ruby
78
87
 
79
88
  DEPENDENCIES
89
+ activesupport (~> 5.0)
80
90
  bright_serializer!
81
91
  bundler (~> 2)
82
92
  coveralls_reborn
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_development_dependency 'activesupport', '~> 5.0'
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,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'oj'
4
- require 'set'
5
4
  require_relative 'attribute'
5
+ require_relative 'attribute_relation'
6
6
  require_relative 'inflector'
7
7
  require_relative 'entity/base'
8
+ require_relative 'extensions'
8
9
 
9
10
  module BrightSerializer
10
11
  module Serializer
12
+ include Extensions
13
+
11
14
  SUPPORTED_TRANSFORMATION = %i[camel camel_lower dash underscore].freeze
12
15
  DEFAULT_OJ_OPTIONS = { mode: :compat, time_format: :ruby, use_to_json: true }.freeze
13
16
 
@@ -20,14 +23,11 @@ module BrightSerializer
20
23
  def initialize(object, **options)
21
24
  @object = object
22
25
  @params = options.delete(:params)
23
-
24
- fields = options.delete(:fields)
25
- @fields = fields ? Set.new(fields) : nil
26
+ @fields = options.delete(:fields)
26
27
  end
27
28
 
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)
29
+ def serialize(object, attributes_to_serialize)
30
+ attributes_to_serialize.each_with_object({}) do |attribute, result|
31
31
  next unless attribute.condition?(object, @params)
32
32
 
33
33
  result[attribute.transformed_key] = attribute.serialize(self, object, @params)
@@ -36,9 +36,9 @@ module BrightSerializer
36
36
 
37
37
  def serializable_hash
38
38
  if @object.respond_to?(:each) && !@object.respond_to?(:each_pair)
39
- @object.map { |o| serialize o }
39
+ @object.map { |o| serialize(o, instance_attributes_to_serialize) }
40
40
  else
41
- serialize(@object)
41
+ serialize(@object, instance_attributes_to_serialize)
42
42
  end
43
43
  end
44
44
 
@@ -70,9 +70,20 @@ module BrightSerializer
70
70
 
71
71
  alias attribute attributes
72
72
 
73
+ def has_one(key, serializer:, **options, &block) # rubocop:disable Naming/PredicateName
74
+ attribute = AttributeRelation.new(
75
+ key, serializer, options.delete(:if), options.delete(:entity), options, &block
76
+ )
77
+ attribute.transformed_key = run_transform_key(key)
78
+ @attributes_to_serialize << attribute
79
+ end
80
+
81
+ alias has_many has_one
82
+ alias belongs_to has_one
83
+
73
84
  def set_key_transform(transform_name) # rubocop:disable Naming/AccessorMethodName
74
85
  unless SUPPORTED_TRANSFORMATION.include?(transform_name)
75
- raise ArgumentError "Invalid transformation: #{SUPPORTED_TRANSFORMATION}"
86
+ raise ArgumentError, "Invalid transformation: #{SUPPORTED_TRANSFORMATION}"
76
87
  end
77
88
 
78
89
  @transform_method = transform_name
@@ -100,5 +111,18 @@ module BrightSerializer
100
111
  name.split('::').last.downcase
101
112
  end
102
113
  end
114
+
115
+ private
116
+
117
+ def instance_attributes_to_serialize
118
+ @instance_attributes_to_serialize ||=
119
+ if @fields.nil?
120
+ self.class.attributes_to_serialize
121
+ else
122
+ self.class.attributes_to_serialize.select do |field|
123
+ @fields.include?(field.key)
124
+ end
125
+ end
126
+ end
103
127
  end
104
128
  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.0'
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.0
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: 2022-11-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.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
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
  - - ">="