ar_serializer 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +22 -0
- data/Gemfile.lock +30 -33
- data/README.md +9 -8
- data/gemfiles/Gemfile-rails-6 +8 -0
- data/gemfiles/Gemfile-rails-7 +8 -0
- data/lib/ar_serializer/field.rb +132 -77
- data/lib/ar_serializer/graphql/parser.rb +3 -3
- data/lib/ar_serializer/graphql/types.rb +16 -6
- data/lib/ar_serializer/serializer.rb +153 -100
- data/lib/ar_serializer/type_script.rb +41 -13
- data/lib/ar_serializer/version.rb +1 -1
- data/lib/ar_serializer.rb +37 -33
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a5c3a6d707be1abec6c92335dbb5888b120a53b6e429eb1849b2aca42a1f1d
|
4
|
+
data.tar.gz: bdd382384bd5452fc909b26cb25fbefd252117fb552b0fc42bc4eddad016a13d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 (
|
12
|
-
activesupport (=
|
13
|
-
activerecord (
|
14
|
-
activemodel (=
|
15
|
-
activesupport (=
|
16
|
-
|
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 (>=
|
20
|
-
minitest (
|
21
|
-
tzinfo (~>
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
39
|
-
simplecov-html (0.
|
40
|
-
|
41
|
-
|
42
|
-
top_n_loader (1.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 (
|
45
|
-
|
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
|
-
|
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
|
-
|
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:
|
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: {
|
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
|
data/lib/ar_serializer/field.rb
CHANGED
@@ -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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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.
|
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.
|
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: :
|
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, '
|
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
|
-
|
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,
|
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,
|
170
|
+
type_from_column_type klass, name.underscore
|
153
171
|
elsif klass.respond_to? :attribute_types
|
154
|
-
type_from_attribute_type(klass,
|
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,
|
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 =
|
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 ||=
|
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
|
-
|
179
|
-
|
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
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
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
|
196
|
-
|
197
|
-
raise ArSerializer::InvalidQuery, "
|
198
|
-
raise ArSerializer::InvalidQuery, "
|
199
|
-
[
|
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,
|
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,
|
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
|
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
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
226
|
-
return
|
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.
|
229
|
-
records = records_nils.sort_by(&:id) + records_nonnils.sort_by { |r| [r[
|
230
|
-
records.reverse! if
|
231
|
-
[model.id
|
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(
|
13
|
-
new(
|
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, {
|
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.
|
149
|
-
type = [type[0...-1], nil] if type.is_a?(String) && type.
|
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.
|
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::
|
4
|
-
|
5
|
-
|
6
|
-
@
|
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
|
10
|
-
|
10
|
+
def ar_custom_serializable_data(result)
|
11
|
+
@block.call result
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
module ArSerializer::
|
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,
|
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(
|
34
|
+
attributes = parse_args(query)[:attributes]
|
43
35
|
if model.is_a?(ArSerializer::Serializable)
|
44
|
-
|
45
|
-
|
46
|
-
output
|
36
|
+
result = _serialize [model], attributes, context, permission: permission
|
37
|
+
result[model]
|
47
38
|
else
|
48
|
-
|
49
|
-
|
50
|
-
|
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(
|
58
|
-
|
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
|
61
|
-
|
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, {}] }.
|
67
|
-
attributes.delete :*
|
55
|
+
attributes = all_keys.map { |k| [k, {}] } + attributes.reject { |k, _| k == :* }
|
68
56
|
end
|
69
|
-
attributes.
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
126
|
-
|
127
|
-
output[column_name] = data
|
136
|
+
sub_results[model] = [:single, child]
|
137
|
+
sub_models << child
|
128
138
|
else
|
129
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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.
|
194
|
+
def self.deep_underscore_keys params
|
141
195
|
case params
|
142
196
|
when Array
|
143
|
-
params.map { |v|
|
197
|
+
params.map { |v| deep_underscore_keys v }
|
144
198
|
when Hash
|
145
|
-
params.transform_keys
|
146
|
-
|
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
|
-
|
165
|
-
|
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
|
170
|
-
attributes.
|
171
|
-
elsif sym_key == :params
|
172
|
-
params =
|
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
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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} =
|
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
|
-
#{
|
60
|
+
#{base_query_type_definition}
|
33
61
|
}
|
34
62
|
TYPE
|
35
63
|
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(
|
8
|
-
Serializer.serialize(
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 |=
|
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
|
60
|
-
|
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(
|
68
|
-
serializer_field
|
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::
|
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.
|
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:
|
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.
|
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: []
|