ar_serializer 1.0.0 → 1.2.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: 7e9144cab6ef520e2c0734bbe98a13a046f3b7b8411310ccc522dc999bae5365
4
- data.tar.gz: d938b8684bbb22937c97c2572994b6bd11b38c72011f1b19975d96c7633656c7
3
+ metadata.gz: 87a5c3a6d707be1abec6c92335dbb5888b120a53b6e429eb1849b2aca42a1f1d
4
+ data.tar.gz: bdd382384bd5452fc909b26cb25fbefd252117fb552b0fc42bc4eddad016a13d
5
5
  SHA512:
6
- metadata.gz: a7ff7ed23abaabef6893c31dc259ce18eeb3111df652c4e4a2d717523853e4a4f27119ddb0d027583ae788b218fdea73abebfc5de99480447e51bbabe04929f9
7
- data.tar.gz: 8bceb19d2f319772e575c440870d074431f21efeab16375dced2e87637791e3d94927f0024c01bfd9669f0e0fb9743539f17ac47b12233a056db64e12b952264
6
+ metadata.gz: 86a9f5895dd2c9f5a9d3a1e51781d5286c4cfbb02687d2d6a9ec502ed85c0971a0343a08218ee0be3233a8f93e0be993e1ce7ab68b5fe75548a76e068247b3a9
7
+ data.tar.gz: 829e8871640abf2a4863d77df8ce703d9e85b38cf46496bd8573f6913572c5ebe18fc1a6c799e1b68124835abc1398841e810413b30cbddb5fe174b0301c85b1
@@ -0,0 +1,22 @@
1
+ name: Test
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ ruby: [ '2.7', '3.0', '3.1' ]
9
+ gemfiles:
10
+ - gemfiles/Gemfile-rails-6
11
+ - gemfiles/Gemfile-rails-7
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ - run: |
19
+ sudo apt-get update
20
+ sudo apt-get install -y libsqlite3-dev
21
+ - run: bundle install --gemfile ${{ matrix.gemfiles }} --jobs 4 --retry 3
22
+ - run: bundle exec --gemfile ${{ matrix.gemfiles }} rake
data/Gemfile.lock CHANGED
@@ -1,48 +1,45 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ar_serializer (1.0.0)
4
+ ar_serializer (1.2.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 (7.0.2.3)
12
+ activesupport (= 7.0.2.3)
13
+ activerecord (7.0.2.3)
14
+ activemodel (= 7.0.2.3)
15
+ activesupport (= 7.0.2.3)
16
+ activesupport (7.0.2.3)
18
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
- i18n (>= 0.7, < 2)
20
- minitest (~> 5.1)
21
- tzinfo (~> 1.1)
22
- arel (9.0.0)
23
- coderay (1.1.2)
24
- concurrent-ruby (1.1.5)
25
- docile (1.3.1)
26
- i18n (1.6.0)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ coderay (1.1.3)
22
+ concurrent-ruby (1.1.10)
23
+ docile (1.4.0)
24
+ i18n (1.10.0)
27
25
  concurrent-ruby (~> 1.0)
28
- json (2.1.0)
29
- method_source (0.9.2)
30
- minitest (5.11.3)
31
- pry (0.12.2)
32
- coderay (~> 1.1.0)
33
- method_source (~> 0.9.0)
34
- rake (12.3.2)
35
- simplecov (0.16.1)
26
+ method_source (1.0.0)
27
+ minitest (5.15.0)
28
+ pry (0.14.1)
29
+ coderay (~> 1.1)
30
+ method_source (~> 1.0)
31
+ rake (13.0.6)
32
+ simplecov (0.21.2)
36
33
  docile (~> 1.1)
37
- json (>= 1.8, < 3)
38
- simplecov-html (~> 0.10.0)
39
- simplecov-html (0.10.2)
40
- sqlite3 (1.4.0)
41
- thread_safe (0.3.6)
42
- top_n_loader (1.0.0)
34
+ simplecov-html (~> 0.11)
35
+ simplecov_json_formatter (~> 0.1)
36
+ simplecov-html (0.12.3)
37
+ simplecov_json_formatter (0.1.4)
38
+ sqlite3 (1.4.2)
39
+ top_n_loader (1.0.2)
43
40
  activerecord
44
- tzinfo (1.2.5)
45
- thread_safe (~> 0.1)
41
+ tzinfo (2.0.4)
42
+ concurrent-ruby (~> 1.0)
46
43
 
47
44
  PLATFORMS
48
45
  ruby
@@ -56,4 +53,4 @@ DEPENDENCIES
56
53
  sqlite3
57
54
 
58
55
  BUNDLED WITH
59
- 1.17.2
56
+ 2.3.3
data/README.md CHANGED
@@ -71,10 +71,10 @@ end
71
71
 
72
72
  # preloader
73
73
  class Foo < ActiveRecord::Base
74
- define_preloader :bar_count_loader do |models|
74
+ bar_count_loader = ->(models) do
75
75
  Bar.where(foo_id: models.map(&:id)).group(:foo_id).count
76
76
  end
77
- serializer_field :bar_count, preload: preloader_name_or_proc do |preloaded|
77
+ serializer_field :bar_count, preload: bar_count_loader do |preloaded|
78
78
  preloaded[id] || 0
79
79
  end
80
80
  # data_blockが `do |preloaded| preloaded[id] end` の場合は省略可能
@@ -85,11 +85,12 @@ 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_by: :createdAt, direction: :desc, first: 10 }] }
89
+ ArSerializer.serialize Post.all, { comments: [:id, params: { order_by: :updatedAt, last: 10 }] }
89
90
 
90
91
  # context and params
91
92
  class Post < ActiveRecord::Base
92
- serializer_field :created_at do |context, params|
93
+ serializer_field :created_at do |context, **params|
93
94
  created_at.in_time_zone(context[:tz]).strftime params[:format]
94
95
  end
95
96
  end
@@ -123,10 +124,10 @@ class User < ActiveRecord::Base
123
124
  serializer_field :o_posts, association: :posts, only: :title
124
125
  serializer_field :e_posts, association: :posts, except: :comments
125
126
  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
127
+ ArSerializer.serialize user, { o_posts: :title, e_posts: :body }
128
+ ArSerializer.serialize user, { o_posts: :*, e_posts: :* }
129
+ ArSerializer.serialize user, { o_posts: :body } #=> Error
130
+ ArSerializer.serialize user, { e_posts: :comments } #=> Error
130
131
 
131
132
  # types
132
133
  class User < ActiveRecord::Base
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ar_serializer.gemspec
6
+ gemspec path: ".."
7
+
8
+ gem "activerecord", "~> 6.0.0"
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ar_serializer.gemspec
6
+ gemspec path: ".."
7
+
8
+ gem "activerecord", "~> 7.0.0"
@@ -1,18 +1,36 @@
1
1
  require 'ar_serializer/error'
2
+ require 'top_n_loader'
3
+ require 'set'
2
4
 
3
5
  class ArSerializer::Field
4
- attr_reader :includes, :preloaders, :data_block, :only, :except, :order_column
5
- def initialize includes: nil, preloaders: [], data_block:, only: nil, except: nil, order_column: nil, type: nil, params_type: nil
6
+ attr_reader :includes, :preloaders, :data_block, :only, :except, :scoped_access, :order_column, :permission, :fallback
7
+ def initialize klass, name, includes: nil, preloaders: [], data_block:, only: nil, except: nil, private: false, scoped_access: nil, permission: nil, fallback: nil, order_column: nil, orderable: nil, type: nil, params_type: nil
8
+ @klass = klass
9
+ @name = name
6
10
  @includes = includes
7
11
  @preloaders = preloaders
8
12
  @only = only && [*only].map(&:to_s)
9
13
  @except = except && [*except].map(&:to_s)
14
+ @private = private
15
+ @scoped_access = scoped_access.nil? ? true : scoped_access
16
+ @permission = permission
17
+ @fallback = fallback
10
18
  @data_block = data_block
11
19
  @order_column = order_column
20
+ @orderable = orderable
12
21
  @type = type
13
22
  @params_type = params_type
14
23
  end
15
24
 
25
+ def orderable?
26
+ return @orderable unless @orderable.nil?
27
+ @orderable = !@permission && @klass.has_attribute?((@order_column || @name).to_s.underscore)
28
+ end
29
+
30
+ def private?
31
+ @private
32
+ end
33
+
16
34
  def type
17
35
  type = @type.is_a?(Proc) ? @type.call : @type
18
36
  splat = lambda do |t|
@@ -33,7 +51,7 @@ class ArSerializer::Field
33
51
  end
34
52
 
35
53
  def arguments
36
- return @params_type if @params_type
54
+ return @params_type.is_a?(Proc) ? @params_type.call : @params_type if @params_type
37
55
  @preloaders.size
38
56
  @data_block.parameters
39
57
  parameters_list = [@data_block.parameters.drop(@preloaders.size + 1)]
@@ -63,39 +81,38 @@ class ArSerializer::Field
63
81
  end
64
82
  return :any if any && arguments.empty?
65
83
  arguments.map do |key, req|
66
- type = key.to_s.match?(/^(.+_)?id|Id$/) ? :int : :any
67
- name = key.to_s.underscore
68
- type = [type] if name.singularize.pluralize == name
69
- [req ? key : "#{key}?", type]
84
+ camelcase = key.to_s.camelcase :lower
85
+ type = (
86
+ case key
87
+ when /^(.+_)?id$/
88
+ :int
89
+ when /^(.+_)?ids$/
90
+ [:int]
91
+ else
92
+ name = key.to_s.underscore
93
+ name.singularize.pluralize == name ? [:any] : :any
94
+ end
95
+ )
96
+ [req ? camelcase : "#{camelcase}?", type]
70
97
  end.to_h
71
98
  end
72
99
 
73
100
  def validate_attributes(attributes)
74
101
  return unless @only || @except
75
- keys = attributes.keys.map(&:to_s) - ['*']
102
+ keys = attributes.map(&:first).map(&:to_s) - ['*']
76
103
  return unless (@only && (keys - @only).present?) || (@except && (keys & @except).present?)
77
104
  invalid_keys = [*(@only && keys - @only), *(@except && keys & @except)].uniq
78
105
  raise ArSerializer::InvalidQuery, "unpermitted attribute: #{invalid_keys}"
79
106
  end
80
107
 
81
- def self.count_field(klass, association_name)
108
+ def self.count_field(klass, name, association_name, permission:)
82
109
  preloader = lambda do |models|
83
110
  klass.joins(association_name).where(id: models.map(&:id)).group(:id).count
84
111
  end
85
- data_block = lambda do |preloaded, _context, _params|
112
+ data_block = lambda do |preloaded, _context, **_params|
86
113
  preloaded[id] || 0
87
114
  end
88
- new preloaders: [preloader], data_block: data_block, type: :int
89
- end
90
-
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
115
+ new klass, name, preloaders: [preloader], data_block: data_block, type: :int, orderable: false, permission: permission, fallback: 0
99
116
  end
100
117
 
101
118
  def self.type_from_column_type(klass, name)
@@ -107,7 +124,7 @@ class ArSerializer::Field
107
124
  def self.type_from_attribute_type(klass, name)
108
125
  attr_type = klass.attribute_types[name]
109
126
  if attr_type.is_a?(ActiveRecord::Enum::EnumType) && klass.respond_to?(name.pluralize)
110
- values = klass.send(name.pluralize).keys.compact
127
+ values = klass.__send__(name.pluralize).keys.compact
111
128
  values = values.map { |v| v.is_a?(Symbol) ? v.to_s : v }.uniq
112
129
  valid_classes = [TrueClass, FalseClass, String, Integer, Float]
113
130
  return if values.empty? || (values.map(&:class) - valid_classes).present?
@@ -120,7 +137,7 @@ class ArSerializer::Field
120
137
  decimal: :float,
121
138
  string: :string,
122
139
  text: :string,
123
- json: :string,
140
+ json: :unknown,
124
141
  binary: :string,
125
142
  time: :string,
126
143
  date: :string,
@@ -128,107 +145,145 @@ class ArSerializer::Field
128
145
  }[attr_type.type]
129
146
  end
130
147
 
131
- def self.create(klass, name, type: nil, params_type: nil, count_of: nil, includes: nil, preload: nil, only: nil, except: nil, order_column: nil, &data_block)
148
+ def self.create(klass, name, type: nil, params_type: nil, count_of: nil, includes: nil, preload: nil, only: nil, except: nil, private: nil, scoped_access: nil, permission: nil, fallback: nil, order_column: nil, orderable: nil, &data_block)
149
+ name = name.to_s
132
150
  if count_of
133
- if includes || preload || data_block || only || except
134
- raise ArgumentError, 'includes, preload block cannot be used with count_of'
151
+ if includes || preload || data_block || only || except || order_column || orderable || scoped_access != nil || fallback
152
+ raise ArgumentError, 'wrong options for count_of field'
135
153
  end
136
- return count_field klass, count_of
154
+ return count_field klass, name, count_of, permission: permission
137
155
  end
138
- underscore_name = name.to_s.underscore
139
- association = klass.reflect_on_association underscore_name if klass.respond_to? :reflect_on_association
156
+ association = klass.reflect_on_association name.underscore if klass.respond_to? :reflect_on_association
140
157
  if association
141
158
  if association.collection?
142
159
  type ||= -> { [association.klass] }
160
+ fallback ||= []
143
161
  elsif (association.belongs_to? && association.options[:optional] == true) || (association.has_one? && association.options[:required] != true)
144
162
  type ||= -> { [association.klass, nil] }
145
163
  else
146
164
  type ||= -> { association.klass }
147
165
  end
148
- return association_field klass, underscore_name, only: only, except: except, type: type, collection: association.collection? if !includes && !preload && !data_block && !params_type
166
+ return association_field klass, name, only: only, except: except, scoped_access: scoped_access, permission: permission, fallback: fallback, type: type, collection: association.collection? if !includes && !preload && !data_block && !params_type
149
167
  end
150
168
  type ||= lambda do
151
169
  if klass.respond_to? :column_for_attribute
152
- type_from_column_type klass, underscore_name
170
+ type_from_column_type klass, name.underscore
153
171
  elsif klass.respond_to? :attribute_types
154
- type_from_attribute_type(klass, underscore_name) || :any
172
+ type_from_attribute_type(klass, name.underscore) || :any
155
173
  else
156
174
  :any
157
175
  end
158
176
  end
159
- custom_field klass, underscore_name, includes: includes, preload: preload, only: only, except: except, order_column: order_column, type: type, params_type: params_type, &data_block
177
+ custom_field klass, name, includes: includes, preload: preload, only: only, except: except, private: private, scoped_access: scoped_access, permission: permission, fallback: fallback, order_column: order_column, orderable: orderable, type: type, params_type: params_type, &data_block
160
178
  end
161
179
 
162
- def self.custom_field(klass, name, includes:, preload:, only:, except:, order_column:, type:, params_type:, &data_block)
180
+ def self.custom_field(klass, name, includes:, preload:, only:, except:, private:, scoped_access:, permission:, fallback:, order_column:, orderable:, type:, params_type:, &data_block)
181
+ underscore_name = name.underscore
163
182
  if preload
164
- preloaders = Array(preload).map do |preloader|
165
- next preloader if preloader.is_a? Proc
166
- unless klass._custom_preloaders.has_key?(preloader)
167
- raise ArgumentError, "preloader not found: #{preloader}"
168
- end
169
- klass._custom_preloaders[preloader]
170
- end
183
+ preloaders = [*preload]
171
184
  else
172
185
  preloaders = []
173
- includes ||= name if klass.respond_to?(:reflect_on_association) && klass.reflect_on_association(name)
186
+ includes ||= underscore_name if klass.respond_to?(:reflect_on_association) && klass.reflect_on_association(underscore_name)
187
+ end
188
+ if !data_block && preloaders.size == 1
189
+ data_block = ->(preloaded, _context, **_params) do
190
+ if preloaded.is_a? Set
191
+ preloaded.include? id
192
+ elsif fallback.nil?
193
+ preloaded[id] # returns preloaded.default unless preloaded.has_key?(id)
194
+ else
195
+ preloaded.has_key?(id) ? preloaded[id] : fallback
196
+ end
197
+ end
174
198
  end
175
- data_block ||= ->(preloaded, _context, _params) { preloaded[id] } if preloaders.size == 1
176
199
  raise ArgumentError, 'data_block needed if multiple preloaders are present' if !preloaders.empty? && data_block.nil?
177
200
  new(
178
- 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 }
201
+ klass,
202
+ name,
203
+ includes: includes, preloaders: preloaders, only: only, except: except,
204
+ private: private, scoped_access: scoped_access, permission: permission, fallback: fallback,
205
+ order_column: order_column, orderable: orderable, type: type, params_type: params_type,
206
+ data_block: data_block || ->(_context, **_params) { __send__ underscore_name }
180
207
  )
181
208
  end
182
209
 
183
- def self.parse_order(klass, order)
184
- key, mode = begin
210
+ def self.parse_order(klass, order: nil, order_by: nil, direction: nil, only: nil, except: nil)
211
+ raise ArSerializer::InvalidQuery, 'invalid order' if order && (order_by || direction)
212
+ primary_key = klass.primary_key.to_sym
213
+ order_by = order_by&.to_s&.to_sym || primary_key
214
+ direction = direction&.to_s&.to_sym || :asc
215
+ if order # deprecated
185
216
  case order
186
217
  when Hash
187
218
  raise ArSerializer::InvalidQuery, 'invalid order' unless order.size == 1
188
- order.first
219
+ order_by, direction = order.first.map(&:to_sym)
189
220
  when Symbol, 'asc', 'desc'
190
- [klass.primary_key, order]
191
- when NilClass
192
- [klass.primary_key, :asc]
221
+ direction = order.to_sym
222
+ else
223
+ raise ArSerializer::InvalidQuery, 'invalid order'
193
224
  end
194
225
  end
195
- info = klass._serializer_field_info(key)
196
- key = info&.order_column || key.to_s.underscore
197
- raise ArSerializer::InvalidQuery, "unpermitted order key: #{key}" unless klass.has_attribute?(key) && info
198
- raise ArSerializer::InvalidQuery, "invalid order mode: #{mode.inspect}" unless [:asc, :desc, 'asc', 'desc'].include? mode
199
- [key.to_sym, mode.to_sym]
226
+ info = klass._serializer_field_info order_by
227
+ order_column = (info&.order_column || order_by).to_s.underscore.to_sym
228
+ raise ArSerializer::InvalidQuery, "invalid order direction: #{direction}" unless [:asc, :desc].include? direction
229
+ raise ArSerializer::InvalidQuery, "unpermitted order field: #{order_by}" unless order_by == primary_key || (info&.orderable? && (!only || only.include?(order_by)) && !except&.include?(order_by))
230
+ [order_column, direction]
200
231
  end
201
232
 
202
- def self.association_field(klass, name, only:, except:, type:, collection:)
233
+ def self.association_field(klass, name, only:, except:, scoped_access:, permission:, fallback:, type:, collection:)
234
+ underscore_name = name.underscore
235
+ only = [*only] if only
236
+ except = [*except] if except
203
237
  if collection
204
- preloader = lambda do |models, _context, limit: nil, order: nil, **_option|
205
- preload_association klass, models, name, limit: limit, order: order
238
+ preloader = lambda do |models, _context, limit: nil, order: nil, first: nil, last: nil, order_by: nil, direction: nil, **_option|
239
+ preload_association klass, models, underscore_name, limit: limit, order: order, first: first, last: last, order_by: order_by, direction: direction, only: only, except: except
240
+ end
241
+ params_type = -> {
242
+ orderable_keys = klass.reflect_on_association(underscore_name).klass._serializer_orderable_field_keys
243
+ orderable_keys &= only.map(&:to_s) if only
244
+ orderable_keys -= except.map(&:to_s) if except
245
+ orderable_keys |= ['id']
246
+ orderable_keys.sort!
247
+ modes = %w[asc desc]
248
+ {
249
+ first?: :int,
250
+ last?: :int,
251
+ orderBy?: orderable_keys.size == 1 ? orderable_keys.first : orderable_keys,
252
+ direction?: modes
253
+ }
254
+ }
255
+ data_block = lambda do |preloaded, _context, **_params|
256
+ preloaded ? preloaded[id || self] || [] : __send__(underscore_name)
206
257
  end
207
- params_type = { limit?: :int, order?: [{ :* => %w[asc desc] }, 'asc', 'desc'] }
208
258
  else
209
- preloader = lambda do |models, _context, _params|
210
- preload_association klass, models, name
259
+ preloader = lambda do |models, _context, **_params|
260
+ preload_association klass, models, underscore_name
261
+ end
262
+ data_block = lambda do |preloaded, _context, **_params|
263
+ preloaded ? preloaded[id || self] : __send__(underscore_name)
211
264
  end
212
265
  end
213
- data_block = lambda do |preloaded, _context, _params|
214
- preloaded ? preloaded[id] || [] : send(name)
215
- end
216
- new preloaders: [preloader], data_block: data_block, only: only, except: except, type: type, params_type: params_type
266
+ new klass, name, preloaders: [preloader], data_block: data_block, only: only, except: except, scoped_access: scoped_access, permission: permission, fallback: fallback, type: type, params_type: params_type, orderable: false
217
267
  end
218
268
 
219
- def self.preload_association(klass, models, name, limit: nil, order: nil)
220
- limit = limit&.to_i
221
- 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 }
269
+ def self.preload_association(klass, models, name, limit: nil, order: nil, first: nil, last: nil, order_by: nil, direction: nil, only: nil, except: nil)
270
+ raise ArSerializer::InvalidQuery, 'invalid count option' if (limit && (first || last)) || (first && last)
271
+ first = (first || limit)&.to_i
272
+ last = last&.to_i
273
+ order_column, order_direction = parse_order klass.reflect_on_association(name).klass, order: order, order_by: order_by, direction: direction, only: only, except: except
274
+ if first || last
275
+ order_option = { order_column => first ? order_direction : (order_direction == :asc ? :desc : :asc) }
276
+ result = TopNLoader.load_associations klass, models.map(&:id), name, limit: first || last, order: order_option
277
+ result = result.transform_values!(&:reverse!) if last
278
+ return result
224
279
  end
225
- ActiveRecord::Associations::Preloader.new.preload models, name
226
- return if limit.nil? && order.nil?
280
+ ArSerializer.preload_associations models, name
281
+ return models.map { |m| [m.id || m, m.__send__(name)] }.to_h if !order && !order_by && !direction
227
282
  models.map do |model|
228
- records_nonnils, records_nils = model.send(name).partition(&order_key)
229
- records = records_nils.sort_by(&:id) + records_nonnils.sort_by { |r| [r[order_key], r.id] }
230
- records.reverse! if order_mode == :desc
231
- [model.id, limit ? records.take(limit) : records]
283
+ records_nonnils, records_nils = model.__send__(name).partition(&order_column)
284
+ records = records_nils.sort_by(&:id) + records_nonnils.sort_by { |r| [r[order_column], r.id] }
285
+ records.reverse! if order_direction == :desc
286
+ [model.id || model, records]
232
287
  end.to_h
233
288
  end
234
289
  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
@@ -95,7 +103,7 @@ module ArSerializer::GraphQL
95
103
  class InvalidType < StandardError; end
96
104
 
97
105
  def validate!
98
- valid_symbols = %i[number int float string boolean any]
106
+ valid_symbols = %i[number int float string boolean any unknown]
99
107
  invalids = []
100
108
  recursive_validate = lambda do |t|
101
109
  case t
@@ -145,8 +153,8 @@ module ArSerializer::GraphQL
145
153
  end
146
154
 
147
155
  def self.from(type, only = nil, except = nil)
148
- type = [type[0...-1].to_sym, nil] if type.is_a?(Symbol) && type.to_s.ends_with?('?')
149
- type = [type[0...-1], nil] if type.is_a?(String) && type.ends_with?('?')
156
+ type = [type[0...-1].to_sym, nil] if type.is_a?(Symbol) && type.to_s.end_with?('?')
157
+ type = [type[0...-1], nil] if type.is_a?(String) && type.end_with?('?')
150
158
  case type
151
159
  when Class
152
160
  SerializableTypeClass.new type, only, except
@@ -187,6 +195,8 @@ module ArSerializer::GraphQL
187
195
  :boolean
188
196
  when :other
189
197
  :other
198
+ when :unknown
199
+ :unknown
190
200
  else
191
201
  :any
192
202
  end
@@ -208,7 +218,7 @@ module ArSerializer::GraphQL
208
218
  ''
209
219
  when 'boolean'
210
220
  true
211
- when 'any'
221
+ when 'any', 'unknown'
212
222
  nil
213
223
  else
214
224
  type
@@ -219,7 +229,7 @@ module ArSerializer::GraphQL
219
229
  case type
220
230
  when :int, :float
221
231
  'number'
222
- when :string, :number, :boolean
232
+ when :string, :number, :boolean, :unknown
223
233
  type.to_s
224
234
  when Symbol
225
235
  'any'
@@ -258,7 +268,7 @@ module ArSerializer::GraphQL
258
268
  end
259
269
 
260
270
  def sample
261
- type.reject { |k| k.to_s.ends_with? '?' }.transform_values do |v|
271
+ type.reject { |k| k.to_s.end_with? '?' }.transform_values do |v|
262
272
  TypeClass.from(v).sample
263
273
  end
264
274
  end
@@ -1,149 +1,203 @@
1
1
  require 'ar_serializer/error'
2
2
 
3
- class ArSerializer::CompositeValue
4
- def initialize(pairs:, output:)
5
- @pairs = pairs
6
- @output = output
3
+ class ArSerializer::CustomSerializable
4
+ attr_reader :ar_custom_serializable_models
5
+ def initialize(models, &block)
6
+ @ar_custom_serializable_models = models
7
+ @block = block
7
8
  end
8
9
 
9
- def ar_serializer_build_sub_calls
10
- [@output, @pairs]
10
+ def ar_custom_serializable_data(result)
11
+ @block.call result
11
12
  end
12
13
  end
13
14
 
14
- module ArSerializer::ArrayLikeCompositeValue
15
- def ar_serializer_build_sub_calls
16
- output = []
17
- record_elements = []
18
- each do |record|
19
- data = {}
20
- output << data
21
- record_elements << [record, data]
22
- end
23
- [output, record_elements]
24
- end
15
+ module ArSerializer::ArrayLikeSerializable
25
16
  end
26
17
 
27
18
  module ArSerializer::Serializer
28
19
  def self.current_namespaces
29
- Thread.current[:ar_serializer_current_namespaces]
20
+ Thread.current[:ar_serializer_current_namespaces] || [nil]
30
21
  end
31
22
 
32
23
  def self.with_namespaces(namespaces)
33
24
  namespaces_was = Thread.current[:ar_serializer_current_namespaces]
34
- Thread.current[:ar_serializer_current_namespaces] = namespaces
25
+ Thread.current[:ar_serializer_current_namespaces] = Array(namespaces) | [nil]
35
26
  yield
36
27
  ensure
37
28
  Thread.current[:ar_serializer_current_namespaces] = namespaces_was
38
29
  end
39
30
 
40
- def self.serialize(model, args, context: nil, include_id: false, use: nil)
31
+ def self.serialize(model, query, context: nil, use: nil, permission: true)
32
+ return nil if model.nil?
41
33
  with_namespaces use do
42
- attributes = parse_args(args)[:attributes]
34
+ attributes = parse_args(query)[:attributes]
43
35
  if model.is_a?(ArSerializer::Serializable)
44
- output = {}
45
- _serialize [[model, output]], attributes, context, include_id
46
- output
36
+ result = _serialize [model], attributes, context, permission: permission
37
+ result[model]
47
38
  else
48
- sets = model.to_a.map do |record|
49
- [record, {}]
50
- end
51
- _serialize sets, attributes, context, include_id
52
- sets.map(&:last)
39
+ models = model.to_a
40
+ result = _serialize models, attributes, context, permission: permission
41
+ models.map { |m| result[m] }.compact
53
42
  end
54
43
  end
55
44
  end
56
45
 
57
- def self._serialize(mixed_value_outputs, attributes, context, include_id, only = nil, except = nil)
58
- mixed_value_outputs.group_by { |v, _o| v.class }.each do |klass, value_outputs|
46
+ def self._serialize(mixed_models, attributes, context, only: nil, except: nil, permission: true)
47
+ output_for_model = {}
48
+ mixed_models.group_by(&:class).each do |klass, models|
59
49
  next unless klass.respond_to? :_serializer_field_info
60
- models = value_outputs.map(&:first)
61
- value_outputs.each { |value, output| output[:id] = value.id } if include_id && klass.method_defined?(:id)
62
- if attributes[:*]
50
+ models.uniq!
51
+ if attributes.any? { |k, _| k == :* }
63
52
  all_keys = klass._serializer_field_keys.map(&:to_sym)
64
53
  all_keys &= only.map(&:to_sym) if only
65
54
  all_keys -= except.map(&:to_sym) if except
66
- attributes = all_keys.map { |k| [k, {}] }.to_h.merge attributes
67
- attributes.delete :*
55
+ attributes = all_keys.map { |k| [k, {}] } + attributes.reject { |k, _| k == :* }
68
56
  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
72
- ActiveRecord::Associations::Preloader.new.preload models, field.includes if field.includes.present?
57
+ attributes.each do |name, sub_args|
58
+ field_name = sub_args[:field_name] || name
59
+ field = klass._serializer_field_info field_name
60
+ if field.nil? || field.private?
61
+ message = "No serializer field `#{field_name}`#{" namespaces: #{current_namespaces.compact}" if current_namespaces.any?} for #{klass}"
62
+ raise ArSerializer::InvalidQuery, message
63
+ end
64
+ ArSerializer.preload_associations models, field.includes if field.includes.present?
73
65
  end
74
66
 
75
- preloader_params = attributes.flat_map do |name, sub_args|
76
- klass._serializer_field_info(name).preloaders.map do |p|
77
- [p, sub_args[:params]]
67
+ preload = lambda do |preloader, params|
68
+ has_keyword = preloader.parameters.any? { |type, _name| %i[key keyreq keyrest].include? type }
69
+ arity = preloader.arity.abs
70
+ arguments = [models]
71
+ if has_keyword
72
+ arguments << context unless arity == 2
73
+ preloader.call(*arguments, **(params || {}))
74
+ else
75
+ arguments << context unless arity == 1
76
+ preloader.call(*arguments)
78
77
  end
79
78
  end
80
- defaults = klass._serializer_field_info(:defaults)
81
- if defaults
82
- preloader_params += defaults.preloaders.map { |p| [p] }
79
+
80
+ preloader_values = {}
81
+ if permission == true
82
+ permission_field = klass._serializer_field_info :permission
83
+ elsif permission
84
+ permission_field = klass._serializer_field_info permission
85
+ raise ArgumentError, "No permission field #{permission} for #{klass}" unless permission_field
83
86
  end
84
- preloader_values = preloader_params.compact.uniq.map do |key|
85
- preloader, params = key
86
- if preloader.arity < 0
87
- [key, preloader.call(models, context, params || {})]
88
- else
89
- [key, preloader.call(*[models, context, params || {}].take(preloader.arity))]
87
+ if permission_field
88
+ preloadeds = permission_field.preloaders.map do |p|
89
+ preloader_values[[p, nil]] ||= preload.call p, nil
90
90
  end
91
- end.to_h
91
+ models = models.select do |model|
92
+ model.instance_exec(*preloadeds, context, {}, &permission_field.data_block)
93
+ end
94
+ end
92
95
 
96
+ defaults = klass._serializer_field_info :defaults
93
97
  if defaults
94
- preloadeds = defaults.preloaders.map { |p| preloader_values[[p]] } || []
95
- value_outputs.each do |value, output|
96
- data = value.instance_exec(*preloadeds, context, {}, &defaults.data_block)
97
- output.update data
98
+ defaults.preloaders.each do |p|
99
+ preloader_values[[p, nil]] ||= preload.call p, nil
98
100
  end
99
101
  end
100
102
 
103
+ attributes.each do |name, sub_args|
104
+ field_name = sub_args[:field_name] || name
105
+ klass._serializer_field_info(field_name).preloaders.each do |p|
106
+ params = sub_args[:params]
107
+ preloader_values[[p, params]] ||= preload.call p, params
108
+ end
109
+ end
110
+
111
+ models.each do |model|
112
+ output_for_model[model] = {}
113
+ end
114
+
101
115
  attributes.each do |name, sub_arg|
102
116
  params = sub_arg[:params]
103
- sub_calls = []
104
117
  column_name = sub_arg[:column_name] || name
105
- info = klass._serializer_field_info name
118
+ field_name = sub_arg[:field_name] || name
119
+ info = klass._serializer_field_info field_name
106
120
  preloadeds = info.preloaders.map { |p| preloader_values[[p, params]] } || []
107
121
  data_block = info.data_block
108
- value_outputs.each do |value, output|
109
- child = value.instance_exec(*preloadeds, context, params || {}, &data_block)
110
- if child.is_a?(Array) && child.all? { |el| el.is_a? ArSerializer::Serializable }
111
- output[column_name] = child.map do |record|
112
- data = {}
113
- sub_calls << [record, data]
114
- data
115
- end
116
- elsif child.respond_to? :ar_serializer_build_sub_calls
117
- sub_output, record_elements = child.ar_serializer_build_sub_calls
118
- record_elements.each { |o| sub_calls << o }
119
- 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
122
+ permission_block = info.permission
123
+ fallback = info.fallback
124
+ sub_results = {}
125
+ sub_models = []
126
+ models.each do |model|
127
+ next if permission_block && !model.instance_exec(context, **(params || {}), &permission_block)
128
+ child = model.instance_exec(*preloadeds, context, **(params || {}), &data_block)
129
+ if child.is_a?(ArSerializer::ArrayLikeSerializable) || (child.is_a?(Array) && child.any? { |el| el.is_a? ArSerializer::Serializable })
130
+ sub_results[model] = [:multiple, child]
131
+ sub_models << child.grep(ArSerializer::Serializable)
132
+ elsif child.respond_to?(:ar_custom_serializable_models) && child.respond_to?(:ar_custom_serializable_data)
133
+ sub_results[model] = [:custom, child]
134
+ sub_models << child.ar_custom_serializable_models
124
135
  elsif child.is_a? ArSerializer::Serializable
125
- data = {}
126
- sub_calls << [child, data]
127
- output[column_name] = data
136
+ sub_results[model] = [:single, child]
137
+ sub_models << child
128
138
  else
129
- output[column_name] = child
139
+ sub_results[model] = [:data, child]
140
+ end
141
+ end
142
+
143
+ sub_models.flatten!
144
+ sub_models.uniq!
145
+ unless sub_models.empty?
146
+ sub_attributes = sub_arg[:attributes] || {}
147
+ info.validate_attributes sub_attributes
148
+ result = _serialize(
149
+ sub_models,
150
+ sub_attributes,
151
+ context,
152
+ only: info.only,
153
+ except: info.except,
154
+ permission: info.scoped_access
155
+ )
156
+ end
157
+
158
+ models.each do |model|
159
+ data = output_for_model[model]
160
+ type, res = sub_results[model]
161
+ case type
162
+ when :single
163
+ data[column_name] = result[res]
164
+ when :multiple
165
+ arr = data[column_name] = []
166
+ res.each do |r|
167
+ if r.is_a? ArSerializer::Serializable
168
+ arr << result[r] if result.key? r
169
+ else
170
+ arr << r
171
+ end
172
+ end
173
+ when :custom
174
+ data[column_name] = res.ar_custom_serializable_data result || {}
175
+ when :data
176
+ data[column_name] = res
177
+ else
178
+ data[column_name] = fallback
130
179
  end
131
180
  end
132
- next if sub_calls.empty?
133
- sub_attributes = sub_arg[:attributes] || {}
134
- info.validate_attributes sub_attributes
135
- _serialize sub_calls, sub_attributes, context, include_id, info.only, info.except
181
+ end
182
+
183
+ if defaults
184
+ preloadeds = defaults.preloaders.map { |p| preloader_values[[p]] } || []
185
+ models.each do |model|
186
+ data = model.instance_exec(*preloadeds, context, {}, &defaults.data_block)
187
+ output_for_model[model].update data
188
+ end
136
189
  end
137
190
  end
191
+ output_for_model
138
192
  end
139
193
 
140
- def self.deep_with_indifferent_access params
194
+ def self.deep_underscore_keys params
141
195
  case params
142
196
  when Array
143
- params.map { |v| deep_with_indifferent_access v }
197
+ params.map { |v| deep_underscore_keys v }
144
198
  when Hash
145
- params.transform_keys(&:to_sym).transform_values! do |v|
146
- deep_with_indifferent_access v
199
+ params.transform_keys { |k| k.to_s.underscore.to_sym }.transform_values! do |v|
200
+ deep_underscore_keys v
147
201
  end
148
202
  else
149
203
  params
@@ -151,27 +205,26 @@ module ArSerializer::Serializer
151
205
  end
152
206
 
153
207
  def self.parse_args(args, only_attributes: false)
154
- attributes = {}
208
+ attributes = []
155
209
  params = nil
156
210
  column_name = nil
211
+ field_name = nil
157
212
  (args.is_a?(Array) ? args : [args]).each do |arg|
158
213
  if arg.is_a?(Symbol) || arg.is_a?(String)
159
- attributes[arg.to_sym] = {}
214
+ attributes << [arg.to_sym, {}]
160
215
  elsif arg.is_a? Hash
161
216
  arg.each do |key, value|
162
217
  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
218
+ if !only_attributes && sym_key == :field
219
+ field_name = value
220
+ elsif !only_attributes && sym_key == :as
168
221
  column_name = value
169
- elsif sym_key == :attributes
170
- attributes.update parse_args(value, only_attributes: true)
171
- elsif sym_key == :params
172
- params = deep_with_indifferent_access value
222
+ elsif !only_attributes && %i[attributes query].include?(sym_key)
223
+ attributes.concat parse_args(value, only_attributes: true)
224
+ elsif !only_attributes && sym_key == :params
225
+ params = deep_underscore_keys value
173
226
  else
174
- attributes[sym_key] = value == true ? {} : parse_args(value)
227
+ attributes << [sym_key, value == true ? {} : parse_args(value)]
175
228
  end
176
229
  end
177
230
  else
@@ -179,6 +232,6 @@ module ArSerializer::Serializer
179
232
  end
180
233
  end
181
234
  return attributes if only_attributes
182
- { attributes: attributes, column_name: column_name, params: params || {} }
235
+ { attributes: attributes, column_name: column_name, field_name: field_name, params: params || {} }
183
236
  end
184
237
  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.2.0'
3
3
  end
data/lib/ar_serializer.rb CHANGED
@@ -4,8 +4,18 @@ 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
+ end
10
+
11
+ if ActiveRecord::VERSION::MAJOR >= 7
12
+ def self.preload_associations(models, associations)
13
+ ActiveRecord::Associations::Preloader.new(records: models, associations: associations).call
14
+ end
15
+ else
16
+ def self.preload_associations(models, associations)
17
+ ActiveRecord::Associations::Preloader.new.preload models, associations
18
+ end
9
19
  end
10
20
  end
11
21
 
@@ -18,60 +28,54 @@ module ArSerializer::Serializable
18
28
  end
19
29
 
20
30
  def _serializer_field_info(name)
21
- namespaces = ArSerializer::Serializer.current_namespaces
22
- if namespaces
23
- Array(namespaces).each do |ns|
24
- field = _serializer_namespace(ns)[name.to_s]
25
- return field if field
26
- end
27
- end
28
- field = _serializer_namespace(nil)[name.to_s]
29
- if field
30
- field
31
- elsif superclass < ArSerializer::Serializable
32
- superclass._serializer_field_info name
31
+ ArSerializer::Serializer.current_namespaces.each do |ns|
32
+ field = _serializer_namespace(ns)[name.to_s]
33
+ return field if field
33
34
  end
35
+ superclass._serializer_field_info name if superclass < ArSerializer::Serializable
34
36
  end
35
37
 
36
- def _serializer_field_keys
37
- namespaces = ArSerializer::Serializer.current_namespaces
38
- keys = []
39
- if namespaces
40
- Array(namespaces).each do |ns|
41
- keys |= _serializer_namespace(ns).keys
38
+ def _serializer_field_keys(public_only = true)
39
+ keys = ArSerializer::Serializer.current_namespaces.map do |ns|
40
+ if public_only
41
+ fields = _serializer_namespace(ns)
42
+ fields.keys.reject { |key| fields[key].private? }
43
+ else
44
+ _serializer_namespace(ns).keys
42
45
  end
43
- end
44
- keys |= _serializer_namespace(nil).keys
45
- keys |= superclass._serializer_field_keys if superclass < ArSerializer::Serializable
46
+ end.inject(:|)
47
+ keys |= superclass._serializer_field_keys(public_only) if superclass < ArSerializer::Serializable
46
48
  keys
47
49
  end
48
50
 
51
+ def _serializer_orderable_field_keys
52
+ _serializer_field_keys.select do |name|
53
+ _serializer_field_info(name).orderable?
54
+ end
55
+ end
56
+
49
57
  def serializer_field(*names, namespace: nil, association: nil, **option, &data_block)
50
58
  namespaces = namespace.is_a?(Array) ? namespace : [namespace]
51
59
  namespaces.each do |ns|
52
60
  names.each do |name|
53
- field = ArSerializer::Field.create(self, association || name, option, &data_block)
61
+ field = ArSerializer::Field.create(self, association || name, **option, &data_block)
54
62
  _serializer_namespace(ns)[name.to_s] = field
55
63
  end
56
64
  end
57
65
  end
58
66
 
59
- def _custom_preloaders
60
- @_custom_preloaders ||= {}
61
- end
62
-
63
- def define_preloader(name, &block)
64
- _custom_preloaders[name] = block
67
+ def serializer_permission(**args, &data_block)
68
+ serializer_field(:permission, **args, private: true, &data_block)
65
69
  end
66
70
 
67
- def serializer_defaults(*args, &block)
68
- serializer_field :defaults, *args, &block
71
+ def serializer_defaults(**args, &block)
72
+ serializer_field(:defaults, **args, private: true, &block)
69
73
  end
70
74
  end
71
75
  end
72
76
 
73
77
  ActiveRecord::Base.include ArSerializer::Serializable
74
- ActiveRecord::Relation.include ArSerializer::ArrayLikeCompositeValue
78
+ ActiveRecord::Relation.include ArSerializer::ArrayLikeSerializable
75
79
 
76
80
  require 'ar_serializer/graphql'
77
81
  require 'ar_serializer/type_script'
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tompng
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-11 00:00:00.000000000 Z
11
+ date: 2022-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -115,6 +115,7 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".github/workflows/test.yml"
118
119
  - ".gitignore"
119
120
  - ".travis.yml"
120
121
  - Gemfile
@@ -125,6 +126,8 @@ files:
125
126
  - ar_serializer.gemspec
126
127
  - bin/console
127
128
  - bin/setup
129
+ - gemfiles/Gemfile-rails-6
130
+ - gemfiles/Gemfile-rails-7
128
131
  - lib/ar_serializer.rb
129
132
  - lib/ar_serializer/error.rb
130
133
  - lib/ar_serializer/field.rb
@@ -138,7 +141,7 @@ homepage: https://github.com/tompng/ar_serializer
138
141
  licenses:
139
142
  - MIT
140
143
  metadata: {}
141
- post_install_message:
144
+ post_install_message:
142
145
  rdoc_options: []
143
146
  require_paths:
144
147
  - lib
@@ -153,8 +156,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
156
  - !ruby/object:Gem::Version
154
157
  version: '0'
155
158
  requirements: []
156
- rubygems_version: 3.0.1
157
- signing_key:
159
+ rubygems_version: 3.3.3
160
+ signing_key:
158
161
  specification_version: 4
159
162
  summary: ActiveRecord serializer, avoid N+1
160
163
  test_files: []