ar_serializer 1.0.0 → 1.1.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: 7e9144cab6ef520e2c0734bbe98a13a046f3b7b8411310ccc522dc999bae5365
4
- data.tar.gz: d938b8684bbb22937c97c2572994b6bd11b38c72011f1b19975d96c7633656c7
3
+ metadata.gz: f5dcdb4d958a8a3c5f45333e6d046d8070b817e9ace01ff9c5129421117a4d96
4
+ data.tar.gz: 3e37de9cd29f69b6a9fc98a297fc40eb700127403e01604cc779f0225ecd2196
5
5
  SHA512:
6
- metadata.gz: a7ff7ed23abaabef6893c31dc259ce18eeb3111df652c4e4a2d717523853e4a4f27119ddb0d027583ae788b218fdea73abebfc5de99480447e51bbabe04929f9
7
- data.tar.gz: 8bceb19d2f319772e575c440870d074431f21efeab16375dced2e87637791e3d94927f0024c01bfd9669f0e0fb9743539f17ac47b12233a056db64e12b952264
6
+ metadata.gz: e6f6050647dfe4baf84dd2801c3158447c91653dd70894a92a9eedd8c1a3b84e6da8182854ccf380f0c66ed4927774d83ed5dfc8c327d1d9ab9189c71d10d0bc
7
+ data.tar.gz: ac83b599db7be347c547b7bd1a6a3a2b0d28825220df5d862899151fe26d1aa4843a34851e3af683d7ff2245b4d904cfbf191a1a5e837dd7758b57cb7023fb83
data/Gemfile.lock CHANGED
@@ -1,33 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar_serializer (1.0.0)
4
+ ar_serializer (1.1.0)
5
5
  activerecord
6
6
  top_n_loader
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (5.2.3)
12
- activesupport (= 5.2.3)
13
- activerecord (5.2.3)
14
- activemodel (= 5.2.3)
15
- activesupport (= 5.2.3)
16
- arel (>= 9.0)
17
- activesupport (5.2.3)
11
+ activemodel (6.0.2.1)
12
+ activesupport (= 6.0.2.1)
13
+ activerecord (6.0.2.1)
14
+ activemodel (= 6.0.2.1)
15
+ activesupport (= 6.0.2.1)
16
+ activesupport (6.0.2.1)
18
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
18
  i18n (>= 0.7, < 2)
20
19
  minitest (~> 5.1)
21
20
  tzinfo (~> 1.1)
22
- arel (9.0.0)
21
+ zeitwerk (~> 2.2)
23
22
  coderay (1.1.2)
24
23
  concurrent-ruby (1.1.5)
25
24
  docile (1.3.1)
26
- i18n (1.6.0)
25
+ i18n (1.7.0)
27
26
  concurrent-ruby (~> 1.0)
28
27
  json (2.1.0)
29
28
  method_source (0.9.2)
30
- minitest (5.11.3)
29
+ minitest (5.13.0)
31
30
  pry (0.12.2)
32
31
  coderay (~> 1.1.0)
33
32
  method_source (~> 0.9.0)
@@ -39,10 +38,11 @@ GEM
39
38
  simplecov-html (0.10.2)
40
39
  sqlite3 (1.4.0)
41
40
  thread_safe (0.3.6)
42
- top_n_loader (1.0.0)
41
+ top_n_loader (1.0.1)
43
42
  activerecord
44
- tzinfo (1.2.5)
43
+ tzinfo (1.2.6)
45
44
  thread_safe (~> 0.1)
45
+ zeitwerk (2.2.2)
46
46
 
47
47
  PLATFORMS
48
48
  ruby
@@ -56,4 +56,4 @@ DEPENDENCIES
56
56
  sqlite3
57
57
 
58
58
  BUNDLED WITH
59
- 1.17.2
59
+ 2.1.2
data/README.md CHANGED
@@ -85,11 +85,11 @@ class Post < ActiveRecord::Base
85
85
  has_many :comments
86
86
  serializer_field :comments
87
87
  end
88
- ArSerializer.serialize Post.all, comments: [:id, params: { order: { id: :desc }, limit: 2 }]
88
+ ArSerializer.serialize Post.all, { comments: [:id, params: { order: { id: :desc }, limit: 2 }] }
89
89
 
90
90
  # context and params
91
91
  class Post < ActiveRecord::Base
92
- serializer_field :created_at do |context, params|
92
+ serializer_field :created_at do |context, **params|
93
93
  created_at.in_time_zone(context[:tz]).strftime params[:format]
94
94
  end
95
95
  end
@@ -123,10 +123,10 @@ class User < ActiveRecord::Base
123
123
  serializer_field :o_posts, association: :posts, only: :title
124
124
  serializer_field :e_posts, association: :posts, except: :comments
125
125
  end
126
- ArSerializer.serialize user, o_posts: :title, e_posts: :body
127
- ArSerializer.serialize user, o_posts: :*, e_posts: :*
128
- ArSerializer.serialize user, o_posts: :body #=> Error
129
- ArSerializer.serialize user, e_posts: :comments #=> Error
126
+ ArSerializer.serialize user, { o_posts: :title, e_posts: :body }
127
+ ArSerializer.serialize user, { o_posts: :*, e_posts: :* }
128
+ ArSerializer.serialize user, { o_posts: :body } #=> Error
129
+ ArSerializer.serialize user, { e_posts: :comments } #=> Error
130
130
 
131
131
  # types
132
132
  class User < ActiveRecord::Base
data/lib/ar_serializer.rb CHANGED
@@ -4,8 +4,8 @@ require 'ar_serializer/field'
4
4
  require 'active_record'
5
5
 
6
6
  module ArSerializer
7
- def self.serialize(*args)
8
- Serializer.serialize(*args)
7
+ def self.serialize(model, query, **option)
8
+ Serializer.serialize(model, query, **option)
9
9
  end
10
10
  end
11
11
 
@@ -50,7 +50,7 @@ module ArSerializer::Serializable
50
50
  namespaces = namespace.is_a?(Array) ? namespace : [namespace]
51
51
  namespaces.each do |ns|
52
52
  names.each do |name|
53
- field = ArSerializer::Field.create(self, association || name, option, &data_block)
53
+ field = ArSerializer::Field.create(self, association || name, **option, &data_block)
54
54
  _serializer_namespace(ns)[name.to_s] = field
55
55
  end
56
56
  end
@@ -64,8 +64,8 @@ module ArSerializer::Serializable
64
64
  _custom_preloaders[name] = block
65
65
  end
66
66
 
67
- def serializer_defaults(*args, &block)
68
- serializer_field :defaults, *args, &block
67
+ def serializer_defaults(**args, &block)
68
+ serializer_field :defaults, **args, &block
69
69
  end
70
70
  end
71
71
  end
@@ -1,4 +1,5 @@
1
1
  require 'ar_serializer/error'
2
+ require 'top_n_loader'
2
3
 
3
4
  class ArSerializer::Field
4
5
  attr_reader :includes, :preloaders, :data_block, :only, :except, :order_column
@@ -72,7 +73,7 @@ class ArSerializer::Field
72
73
 
73
74
  def validate_attributes(attributes)
74
75
  return unless @only || @except
75
- keys = attributes.keys.map(&:to_s) - ['*']
76
+ keys = attributes.map(&:first).map(&:to_s) - ['*']
76
77
  return unless (@only && (keys - @only).present?) || (@except && (keys & @except).present?)
77
78
  invalid_keys = [*(@only && keys - @only), *(@except && keys & @except)].uniq
78
79
  raise ArSerializer::InvalidQuery, "unpermitted attribute: #{invalid_keys}"
@@ -82,22 +83,12 @@ class ArSerializer::Field
82
83
  preloader = lambda do |models|
83
84
  klass.joins(association_name).where(id: models.map(&:id)).group(:id).count
84
85
  end
85
- data_block = lambda do |preloaded, _context, _params|
86
+ data_block = lambda do |preloaded, _context, **_params|
86
87
  preloaded[id] || 0
87
88
  end
88
89
  new preloaders: [preloader], data_block: data_block, type: :int
89
90
  end
90
91
 
91
- def self.top_n_loader_available?
92
- return @top_n_loader_available unless @top_n_loader_available.nil?
93
- @top_n_loader_available = begin
94
- require 'top_n_loader'
95
- true
96
- rescue LoadError
97
- nil
98
- end
99
- end
100
-
101
92
  def self.type_from_column_type(klass, name)
102
93
  type = type_from_attribute_type klass, name.to_s
103
94
  return :any if type.nil?
@@ -172,11 +163,11 @@ class ArSerializer::Field
172
163
  preloaders = []
173
164
  includes ||= name if klass.respond_to?(:reflect_on_association) && klass.reflect_on_association(name)
174
165
  end
175
- data_block ||= ->(preloaded, _context, _params) { preloaded[id] } if preloaders.size == 1
166
+ data_block ||= ->(preloaded, _context, **_params) { preloaded[id] } if preloaders.size == 1
176
167
  raise ArgumentError, 'data_block needed if multiple preloaders are present' if !preloaders.empty? && data_block.nil?
177
168
  new(
178
169
  includes: includes, preloaders: preloaders, only: only, except: except, order_column: order_column, type: type, params_type: params_type,
179
- data_block: data_block || ->(_context, _params) { send name }
170
+ data_block: data_block || ->(_context, **_params) { send name }
180
171
  )
181
172
  end
182
173
 
@@ -194,7 +185,7 @@ class ArSerializer::Field
194
185
  end
195
186
  info = klass._serializer_field_info(key)
196
187
  key = info&.order_column || key.to_s.underscore
197
- raise ArSerializer::InvalidQuery, "unpermitted order key: #{key}" unless klass.has_attribute?(key) && info
188
+ raise ArSerializer::InvalidQuery, "unpermitted order key: #{key}" unless klass.primary_key == key.to_s || (klass.has_attribute?(key) && info)
198
189
  raise ArSerializer::InvalidQuery, "invalid order mode: #{mode.inspect}" unless [:asc, :desc, 'asc', 'desc'].include? mode
199
190
  [key.to_sym, mode.to_sym]
200
191
  end
@@ -206,11 +197,11 @@ class ArSerializer::Field
206
197
  end
207
198
  params_type = { limit?: :int, order?: [{ :* => %w[asc desc] }, 'asc', 'desc'] }
208
199
  else
209
- preloader = lambda do |models, _context, _params|
200
+ preloader = lambda do |models, _context, **_params|
210
201
  preload_association klass, models, name
211
202
  end
212
203
  end
213
- data_block = lambda do |preloaded, _context, _params|
204
+ data_block = lambda do |preloaded, _context, **_params|
214
205
  preloaded ? preloaded[id] || [] : send(name)
215
206
  end
216
207
  new preloaders: [preloader], data_block: data_block, only: only, except: except, type: type, params_type: params_type
@@ -219,16 +210,14 @@ class ArSerializer::Field
219
210
  def self.preload_association(klass, models, name, limit: nil, order: nil)
220
211
  limit = limit&.to_i
221
212
  order_key, order_mode = parse_order klass.reflect_on_association(name).klass, order
222
- if limit && top_n_loader_available?
223
- return TopNLoader.load_associations klass, models.map(&:id), name, limit: limit, order: { order_key => order_mode }
224
- end
213
+ return TopNLoader.load_associations klass, models.map(&:id), name, limit: limit, order: { order_key => order_mode } if limit
225
214
  ActiveRecord::Associations::Preloader.new.preload models, name
226
- return if limit.nil? && order.nil?
215
+ return if order.nil?
227
216
  models.map do |model|
228
217
  records_nonnils, records_nils = model.send(name).partition(&order_key)
229
218
  records = records_nils.sort_by(&:id) + records_nonnils.sort_by { |r| [r[order_key], r.id] }
230
219
  records.reverse! if order_mode == :desc
231
- [model.id, limit ? records.take(limit) : records]
220
+ [model.id, records]
232
221
  end.to_h
233
222
  end
234
223
  end
@@ -9,8 +9,8 @@ class ArSerializer::GraphQL::Parser
9
9
  @chars = query.chars
10
10
  end
11
11
 
12
- def self.parse(*args)
13
- new(*args).parse
12
+ def self.parse(query, **option)
13
+ new(query, **option).parse
14
14
  end
15
15
 
16
16
  def parse
@@ -194,7 +194,7 @@ class ArSerializer::GraphQL::Parser
194
194
  args = parse_args
195
195
  consume_blank
196
196
  fields = parse_fields
197
- [name, { as: alias_name, params: args, attributes: fields }.compact]
197
+ [(alias_name || name), { field: name, params: args, attributes: fields }.compact]
198
198
  end
199
199
 
200
200
  def parse_fields
@@ -38,7 +38,15 @@ module ArSerializer::GraphQL
38
38
  type.collect_types types
39
39
  end
40
40
 
41
+ def args_required?
42
+ return false if field.arguments == :any
43
+ field.arguments.any? do |key, type|
44
+ !key.match?(/\?$/) && !(type.is_a?(Array) && type.include?(nil))
45
+ end
46
+ end
47
+
41
48
  def args_ts_type
49
+ return 'any' if field.arguments == :any
42
50
  arg_types = field.arguments.map do |key, type|
43
51
  "#{key}: #{TypeClass.from(type).ts_type}"
44
52
  end
@@ -37,9 +37,9 @@ module ArSerializer::Serializer
37
37
  Thread.current[:ar_serializer_current_namespaces] = namespaces_was
38
38
  end
39
39
 
40
- def self.serialize(model, args, context: nil, include_id: false, use: nil)
40
+ def self.serialize(model, query, context: nil, include_id: false, use: nil)
41
41
  with_namespaces use do
42
- attributes = parse_args(args)[:attributes]
42
+ attributes = parse_args(query)[:attributes]
43
43
  if model.is_a?(ArSerializer::Serializable)
44
44
  output = {}
45
45
  _serialize [[model, output]], attributes, context, include_id
@@ -59,21 +59,22 @@ module ArSerializer::Serializer
59
59
  next unless klass.respond_to? :_serializer_field_info
60
60
  models = value_outputs.map(&:first)
61
61
  value_outputs.each { |value, output| output[:id] = value.id } if include_id && klass.method_defined?(:id)
62
- if attributes[:*]
63
- all_keys = klass._serializer_field_keys.map(&:to_sym)
62
+ if attributes.any? { |k, _| k == :* }
63
+ all_keys = klass._serializer_field_keys.map(&:to_sym) - [:defaults]
64
64
  all_keys &= only.map(&:to_sym) if only
65
65
  all_keys -= except.map(&:to_sym) if except
66
- attributes = all_keys.map { |k| [k, {}] }.to_h.merge attributes
67
- attributes.delete :*
66
+ attributes = all_keys.map { |k| [k, {}] } + attributes.reject { |k, _| k == :* }
68
67
  end
69
- attributes.each_key do |name|
70
- field = klass._serializer_field_info name
71
- raise ArSerializer::InvalidQuery, "No serializer field `#{name}`#{" namespaces: #{current_namespaces}" if current_namespaces} for #{klass}" unless field
68
+ attributes.each do |name, sub_args|
69
+ field_name = sub_args[:field_name] || name
70
+ field = klass._serializer_field_info field_name
71
+ raise ArSerializer::InvalidQuery, "No serializer field `#{field_name}`#{" namespaces: #{current_namespaces}" if current_namespaces} for #{klass}" unless field
72
72
  ActiveRecord::Associations::Preloader.new.preload models, field.includes if field.includes.present?
73
73
  end
74
74
 
75
75
  preloader_params = attributes.flat_map do |name, sub_args|
76
- klass._serializer_field_info(name).preloaders.map do |p|
76
+ field_name = sub_args[:field_name] || name
77
+ klass._serializer_field_info(field_name).preloaders.map do |p|
77
78
  [p, sub_args[:params]]
78
79
  end
79
80
  end
@@ -84,9 +85,9 @@ module ArSerializer::Serializer
84
85
  preloader_values = preloader_params.compact.uniq.map do |key|
85
86
  preloader, params = key
86
87
  if preloader.arity < 0
87
- [key, preloader.call(models, context, params || {})]
88
+ [key, preloader.call(models, context, **(params || {}))]
88
89
  else
89
- [key, preloader.call(*[models, context, params || {}].take(preloader.arity))]
90
+ [key, preloader.call(*[models, context].take(preloader.arity), **(params || {}))]
90
91
  end
91
92
  end.to_h
92
93
 
@@ -102,11 +103,12 @@ module ArSerializer::Serializer
102
103
  params = sub_arg[:params]
103
104
  sub_calls = []
104
105
  column_name = sub_arg[:column_name] || name
105
- info = klass._serializer_field_info name
106
+ field_name = sub_arg[:field_name] || name
107
+ info = klass._serializer_field_info field_name
106
108
  preloadeds = info.preloaders.map { |p| preloader_values[[p, params]] } || []
107
109
  data_block = info.data_block
108
110
  value_outputs.each do |value, output|
109
- child = value.instance_exec(*preloadeds, context, params || {}, &data_block)
111
+ child = value.instance_exec(*preloadeds, context, **(params || {}), &data_block)
110
112
  if child.is_a?(Array) && child.all? { |el| el.is_a? ArSerializer::Serializable }
111
113
  output[column_name] = child.map do |record|
112
114
  data = {}
@@ -117,10 +119,6 @@ module ArSerializer::Serializer
117
119
  sub_output, record_elements = child.ar_serializer_build_sub_calls
118
120
  record_elements.each { |o| sub_calls << o }
119
121
  output[column_name] = sub_output
120
- elsif child.is_a? ArSerializer::CompositeValue
121
- sub_output, record_elements = child.build
122
- record_elements.each { |o| sub_calls << o }
123
- output[column_name] = sub_output
124
122
  elsif child.is_a? ArSerializer::Serializable
125
123
  data = {}
126
124
  sub_calls << [child, data]
@@ -151,27 +149,26 @@ module ArSerializer::Serializer
151
149
  end
152
150
 
153
151
  def self.parse_args(args, only_attributes: false)
154
- attributes = {}
152
+ attributes = []
155
153
  params = nil
156
154
  column_name = nil
155
+ field_name = nil
157
156
  (args.is_a?(Array) ? args : [args]).each do |arg|
158
157
  if arg.is_a?(Symbol) || arg.is_a?(String)
159
- attributes[arg.to_sym] = {}
158
+ attributes << [arg.to_sym, {}]
160
159
  elsif arg.is_a? Hash
161
160
  arg.each do |key, value|
162
161
  sym_key = key.to_sym
163
- if only_attributes
164
- attributes[sym_key] = value == true ? {} : parse_args(value)
165
- next
166
- end
167
- if sym_key == :as
162
+ if !only_attributes && sym_key == :field
163
+ field_name = value
164
+ elsif !only_attributes && sym_key == :as
168
165
  column_name = value
169
- elsif sym_key == :attributes
170
- attributes.update parse_args(value, only_attributes: true)
171
- elsif sym_key == :params
166
+ elsif !only_attributes && %i[attributes query].include?(sym_key)
167
+ attributes.concat parse_args(value, only_attributes: true)
168
+ elsif !only_attributes && sym_key == :params
172
169
  params = deep_with_indifferent_access value
173
170
  else
174
- attributes[sym_key] = value == true ? {} : parse_args(value)
171
+ attributes << [sym_key, value == true ? {} : parse_args(value)]
175
172
  end
176
173
  end
177
174
  else
@@ -179,6 +176,6 @@ module ArSerializer::Serializer
179
176
  end
180
177
  end
181
178
  return attributes if only_attributes
182
- { attributes: attributes, column_name: column_name, params: params || {} }
179
+ { attributes: attributes, column_name: column_name, field_name: field_name, params: params || {} }
183
180
  end
184
181
  end
@@ -4,32 +4,60 @@ module ArSerializer::TypeScript
4
4
  def self.generate_type_definition(*classes)
5
5
  types = related_serializer_types classes.flatten
6
6
  [
7
+ 'type NonAliasQuery = true | string | string[] | ({ field?: undefined } & { [key: string]: any })',
7
8
  types.map { |t| data_type_definition t },
8
9
  types.map { |t| query_type_definition t }
9
10
  ].join "\n"
10
11
  end
11
12
 
13
+ FieldInfo = Struct.new :name, :params_required?, :params_type, :query_type, :sub_query_params
14
+
12
15
  def self.query_type_definition(type)
13
16
  field_definitions = type.fields.map do |field|
14
17
  association_type = field.type.association_type
15
- if association_type
16
- qname = "Type#{association_type.name}Query"
17
- if field.args.empty?
18
- "#{field.name}?: true | #{qname} | { as?: string; attributes?: #{qname} }"
19
- else
20
- "#{field.name}?: true | #{qname} | { as?: string; params: #{field.args_ts_type}; attributes?: #{qname} }"
21
- end
22
- else
23
- "#{field.name}?: true | { as: string }"
24
- end
18
+ query_type = "Type#{association_type.name}Query" if association_type
19
+ params_type = field.args_ts_type unless field.args.empty?
20
+ params_required = field.args_required?
21
+ attrs = []
22
+ attrs << "query?: #{query_type}" if query_type
23
+ attrs << "params#{'?' unless params_required}: #{params_type}" if params_type
24
+ sub_query_params = attrs
25
+ FieldInfo.new field.name, params_required, params_type, query_type, sub_query_params
25
26
  end
26
- field_definitions << "'*'?: true"
27
+ accept_wildcard = !field_definitions.any?(&:params_required?)
27
28
  query_type_name = "Type#{type.name}Query"
29
+ standalone_fields_name = "Type#{type.name}StandaloneFields"
30
+ alias_query_type_name = "Type#{type.name}AliasFieldQuery"
28
31
  base_query_type_name = "Type#{type.name}QueryBase"
32
+ standalone_fields_definition = field_definitions.reject(&:params_required?).map do |info|
33
+ "'#{info.name}'"
34
+ end.join(' | ')
35
+ standalone_fields_definition += " | '*'" if accept_wildcard
36
+ alias_query_type_definition = field_definitions.map do |info|
37
+ attrs = ["field: '#{info.name}'", *info.sub_query_params].join('; ')
38
+ " | { #{attrs} }\n"
39
+ end.join
40
+ base_query_type_definition = field_definitions.map do |info|
41
+ types = []
42
+ unless info.params_required?
43
+ types << true
44
+ types << info.query_type if info.query_type
45
+ end
46
+ types << "{ field: never; #{info.sub_query_params.join('; ')} }" unless info.sub_query_params.empty?
47
+ " #{info.name}: #{types.join(' | ')}"
48
+ end.join("\n")
49
+ base_query_type_definition += "\n '*': true" if accept_wildcard
29
50
  <<~TYPE
30
- export type #{query_type_name} = keyof (#{base_query_type_name}) | Readonly<(keyof (#{base_query_type_name}))[]> | #{base_query_type_name}
51
+ export type #{query_type_name} = #{standalone_fields_name} | Readonly<#{standalone_fields_name}[]>
52
+ | (
53
+ { [key in keyof #{base_query_type_name}]?: key extends '*' ? true : #{base_query_type_name}[key] | #{alias_query_type_name} }
54
+ & { [key: string]: #{alias_query_type_name} | NonAliasQuery }
55
+ )
56
+ export type #{standalone_fields_name} = #{standalone_fields_definition.presence || 'never'}
57
+ export type #{alias_query_type_name} =
58
+ #{alias_query_type_definition.presence || 'never'}
31
59
  export interface #{base_query_type_name} {
32
- #{field_definitions.map { |line| " #{line}" }.join("\n")}
60
+ #{base_query_type_definition}
33
61
  }
34
62
  TYPE
35
63
  end
@@ -1,3 +1,3 @@
1
1
  module ArSerializer
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tompng
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-11 00:00:00.000000000 Z
11
+ date: 2020-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  requirements: []
156
- rubygems_version: 3.0.1
156
+ rubygems_version: 3.1.2
157
157
  signing_key:
158
158
  specification_version: 4
159
159
  summary: ActiveRecord serializer, avoid N+1