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 +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: []
|