bright_serializer 0.3.1 → 0.4.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: 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
  - - ">="