graphiti 1.2.16 → 1.3.9
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/ci.yml +96 -0
- data/.standard.yml +4 -4
- data/Appraisals +23 -17
- data/CHANGELOG.md +7 -1
- data/Guardfile +5 -5
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/deprecated_generators/graphiti/resource_generator.rb +1 -1
- data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +2 -2
- data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +3 -4
- data/gemfiles/rails_6.gemfile +1 -1
- data/gemfiles/rails_6_graphiti_rails.gemfile +2 -3
- data/gemfiles/{rails_4.gemfile → rails_7.gemfile} +2 -2
- data/gemfiles/rails_7_graphiti_rails.gemfile +19 -0
- data/graphiti.gemspec +16 -16
- data/lib/graphiti/adapters/abstract.rb +20 -5
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +1 -1
- data/lib/graphiti/adapters/active_record/{inferrence.rb → inference.rb} +2 -2
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +19 -0
- data/lib/graphiti/adapters/active_record.rb +119 -74
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/null.rb +1 -1
- data/lib/graphiti/adapters/persistence/associations.rb +78 -0
- data/lib/graphiti/configuration.rb +3 -1
- data/lib/graphiti/debugger.rb +12 -8
- data/lib/graphiti/delegates/pagination.rb +47 -13
- data/lib/graphiti/deserializer.rb +3 -3
- data/lib/graphiti/errors.rb +109 -15
- data/lib/graphiti/extensions/extra_attribute.rb +4 -4
- data/lib/graphiti/extensions/temp_id.rb +1 -1
- data/lib/graphiti/filter_operators.rb +0 -1
- data/lib/graphiti/hash_renderer.rb +198 -21
- data/lib/graphiti/query.rb +105 -73
- data/lib/graphiti/railtie.rb +5 -5
- data/lib/graphiti/renderer.rb +19 -1
- data/lib/graphiti/request_validator.rb +10 -10
- data/lib/graphiti/request_validators/update_validator.rb +4 -5
- data/lib/graphiti/request_validators/validator.rb +38 -24
- data/lib/graphiti/resource/configuration.rb +35 -7
- data/lib/graphiti/resource/dsl.rb +34 -8
- data/lib/graphiti/resource/interface.rb +13 -3
- data/lib/graphiti/resource/links.rb +3 -3
- data/lib/graphiti/resource/persistence.rb +2 -1
- data/lib/graphiti/resource/polymorphism.rb +8 -2
- data/lib/graphiti/resource/remote.rb +2 -2
- data/lib/graphiti/resource/sideloading.rb +4 -4
- data/lib/graphiti/resource.rb +12 -1
- data/lib/graphiti/resource_proxy.rb +23 -3
- data/lib/graphiti/runner.rb +5 -5
- data/lib/graphiti/schema.rb +36 -11
- data/lib/graphiti/schema_diff.rb +44 -4
- data/lib/graphiti/scope.rb +8 -10
- data/lib/graphiti/scoping/base.rb +3 -3
- data/lib/graphiti/scoping/filter.rb +36 -15
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/scoping/sort.rb +5 -7
- data/lib/graphiti/serializer.rb +49 -7
- data/lib/graphiti/sideload/belongs_to.rb +1 -1
- data/lib/graphiti/sideload/has_many.rb +19 -1
- data/lib/graphiti/sideload/many_to_many.rb +11 -4
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +3 -4
- data/lib/graphiti/sideload.rb +47 -23
- data/lib/graphiti/stats/dsl.rb +0 -1
- data/lib/graphiti/stats/payload.rb +12 -9
- data/lib/graphiti/types.rb +15 -15
- data/lib/graphiti/util/attribute_check.rb +1 -1
- data/lib/graphiti/util/class.rb +6 -0
- data/lib/graphiti/util/link.rb +10 -2
- data/lib/graphiti/util/persistence.rb +21 -78
- data/lib/graphiti/util/relationship_payload.rb +4 -4
- data/lib/graphiti/util/remote_params.rb +9 -4
- data/lib/graphiti/util/remote_serializer.rb +1 -0
- data/lib/graphiti/util/serializer_attributes.rb +41 -11
- data/lib/graphiti/util/simple_errors.rb +4 -4
- data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +6 -3
- metadata +46 -37
- data/.travis.yml +0 -59
@@ -1,7 +1,7 @@
|
|
1
1
|
module Graphiti
|
2
2
|
module Adapters
|
3
3
|
class ActiveRecord < ::Graphiti::Adapters::Abstract
|
4
|
-
require "graphiti/adapters/active_record/
|
4
|
+
require "graphiti/adapters/active_record/inference"
|
5
5
|
require "graphiti/adapters/active_record/has_many_sideload"
|
6
6
|
require "graphiti/adapters/active_record/belongs_to_sideload"
|
7
7
|
require "graphiti/adapters/active_record/has_one_sideload"
|
@@ -12,31 +12,31 @@ module Graphiti
|
|
12
12
|
has_many: Graphiti::Adapters::ActiveRecord::HasManySideload,
|
13
13
|
has_one: Graphiti::Adapters::ActiveRecord::HasOneSideload,
|
14
14
|
belongs_to: Graphiti::Adapters::ActiveRecord::BelongsToSideload,
|
15
|
-
many_to_many: Graphiti::Adapters::ActiveRecord::ManyToManySideload
|
15
|
+
many_to_many: Graphiti::Adapters::ActiveRecord::ManyToManySideload
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
19
|
def filter_eq(scope, attribute, value)
|
20
20
|
scope.where(attribute => value)
|
21
21
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
alias_method :filter_integer_eq, :filter_eq
|
23
|
+
alias_method :filter_float_eq, :filter_eq
|
24
|
+
alias_method :filter_big_decimal_eq, :filter_eq
|
25
|
+
alias_method :filter_date_eq, :filter_eq
|
26
|
+
alias_method :filter_boolean_eq, :filter_eq
|
27
|
+
alias_method :filter_uuid_eq, :filter_eq
|
28
|
+
alias_method :filter_enum_eq, :filter_eq
|
29
29
|
|
30
30
|
def filter_not_eq(scope, attribute, value)
|
31
31
|
scope.where.not(attribute => value)
|
32
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
alias_method :filter_integer_not_eq, :filter_not_eq
|
34
|
+
alias_method :filter_float_not_eq, :filter_not_eq
|
35
|
+
alias_method :filter_big_decimal_not_eq, :filter_not_eq
|
36
|
+
alias_method :filter_date_not_eq, :filter_not_eq
|
37
|
+
alias_method :filter_boolean_not_eq, :filter_not_eq
|
38
|
+
alias_method :filter_uuid_not_eq, :filter_not_eq
|
39
|
+
alias_method :filter_enum_not_eq, :filter_not_eq
|
40
40
|
|
41
41
|
def filter_string_eq(scope, attribute, value, is_not: false)
|
42
42
|
column = column_for(scope, attribute)
|
@@ -57,54 +57,63 @@ module Graphiti
|
|
57
57
|
filter_string_eql(scope, attribute, value, is_not: true)
|
58
58
|
end
|
59
59
|
|
60
|
-
def filter_string_prefix(scope, attribute, value, is_not: false)
|
61
|
-
column = column_for(scope, attribute)
|
62
|
-
map = value.map { |v| "#{v}%" }
|
63
|
-
clause = column.lower.matches_any(map)
|
64
|
-
is_not ? scope.where.not(clause) : scope.where(clause)
|
65
|
-
end
|
66
|
-
|
67
|
-
def filter_string_not_prefix(scope, attribute, value)
|
68
|
-
filter_string_prefix(scope, attribute, value, is_not: true)
|
69
|
-
end
|
70
|
-
|
71
|
-
def filter_string_suffix(scope, attribute, value, is_not: false)
|
72
|
-
column = column_for(scope, attribute)
|
73
|
-
map = value.map { |v| "%#{v}" }
|
74
|
-
clause = column.lower.matches_any(map)
|
75
|
-
is_not ? scope.where.not(clause) : scope.where(clause)
|
76
|
-
end
|
77
|
-
|
78
|
-
def filter_string_not_suffix(scope, attribute, value)
|
79
|
-
filter_string_suffix(scope, attribute, value, is_not: true)
|
80
|
-
end
|
81
|
-
|
82
60
|
# Arel has different match escaping behavior before rails 5.
|
83
61
|
# Since rails 4.x does not expose methods to escape LIKE statements
|
84
62
|
# anyway, we just don't support proper LIKE escaping in those versions.
|
85
|
-
if ::ActiveRecord.version >= Gem::Version.new(
|
63
|
+
if ::ActiveRecord.version >= Gem::Version.new("5.0.0")
|
86
64
|
def filter_string_match(scope, attribute, value, is_not: false)
|
87
|
-
|
88
|
-
column = column_for(scope, attribute)
|
89
|
-
map = value.map do |v|
|
90
|
-
v = v.downcase
|
91
|
-
v = scope.sanitize_sql_like(v)
|
65
|
+
clause = sanitized_like_for(scope, attribute, value) { |v|
|
92
66
|
"%#{v}%"
|
93
|
-
|
94
|
-
|
67
|
+
}
|
68
|
+
is_not ? scope.where.not(clause) : scope.where(clause)
|
69
|
+
end
|
70
|
+
|
71
|
+
def filter_string_prefix(scope, attribute, value, is_not: false)
|
72
|
+
clause = sanitized_like_for(scope, attribute, value) { |v|
|
73
|
+
"#{v}%"
|
74
|
+
}
|
75
|
+
is_not ? scope.where.not(clause) : scope.where(clause)
|
76
|
+
end
|
77
|
+
|
78
|
+
def filter_string_suffix(scope, attribute, value, is_not: false)
|
79
|
+
clause = sanitized_like_for(scope, attribute, value) { |v|
|
80
|
+
"%#{v}"
|
81
|
+
}
|
95
82
|
is_not ? scope.where.not(clause) : scope.where(clause)
|
96
83
|
end
|
97
84
|
else
|
98
85
|
def filter_string_match(scope, attribute, value, is_not: false)
|
99
86
|
column = column_for(scope, attribute)
|
100
|
-
map = value.map
|
87
|
+
map = value.map { |v|
|
101
88
|
"%#{v.downcase}%"
|
102
|
-
|
89
|
+
}
|
90
|
+
clause = column.lower.matches_any(map)
|
91
|
+
is_not ? scope.where.not(clause) : scope.where(clause)
|
92
|
+
end
|
93
|
+
|
94
|
+
def filter_string_prefix(scope, attribute, value, is_not: false)
|
95
|
+
column = column_for(scope, attribute)
|
96
|
+
map = value.map { |v| "#{v}%" }
|
97
|
+
clause = column.lower.matches_any(map)
|
98
|
+
is_not ? scope.where.not(clause) : scope.where(clause)
|
99
|
+
end
|
100
|
+
|
101
|
+
def filter_string_suffix(scope, attribute, value, is_not: false)
|
102
|
+
column = column_for(scope, attribute)
|
103
|
+
map = value.map { |v| "%#{v}" }
|
103
104
|
clause = column.lower.matches_any(map)
|
104
105
|
is_not ? scope.where.not(clause) : scope.where(clause)
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
109
|
+
def filter_string_not_prefix(scope, attribute, value)
|
110
|
+
filter_string_prefix(scope, attribute, value, is_not: true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def filter_string_not_suffix(scope, attribute, value)
|
114
|
+
filter_string_suffix(scope, attribute, value, is_not: true)
|
115
|
+
end
|
116
|
+
|
108
117
|
def filter_string_not_match(scope, attribute, value)
|
109
118
|
filter_string_match(scope, attribute, value, is_not: true)
|
110
119
|
end
|
@@ -113,44 +122,44 @@ module Graphiti
|
|
113
122
|
column = column_for(scope, attribute)
|
114
123
|
scope.where(column.gt_any(value))
|
115
124
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
125
|
+
alias_method :filter_integer_gt, :filter_gt
|
126
|
+
alias_method :filter_float_gt, :filter_gt
|
127
|
+
alias_method :filter_big_decimal_gt, :filter_gt
|
128
|
+
alias_method :filter_datetime_gt, :filter_gt
|
129
|
+
alias_method :filter_date_gt, :filter_gt
|
121
130
|
|
122
131
|
def filter_gte(scope, attribute, value)
|
123
132
|
column = column_for(scope, attribute)
|
124
133
|
scope.where(column.gteq_any(value))
|
125
134
|
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
135
|
+
alias_method :filter_integer_gte, :filter_gte
|
136
|
+
alias_method :filter_float_gte, :filter_gte
|
137
|
+
alias_method :filter_big_decimal_gte, :filter_gte
|
138
|
+
alias_method :filter_datetime_gte, :filter_gte
|
139
|
+
alias_method :filter_date_gte, :filter_gte
|
131
140
|
|
132
141
|
def filter_lt(scope, attribute, value)
|
133
142
|
column = column_for(scope, attribute)
|
134
143
|
scope.where(column.lt_any(value))
|
135
144
|
end
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
145
|
+
alias_method :filter_integer_lt, :filter_lt
|
146
|
+
alias_method :filter_float_lt, :filter_lt
|
147
|
+
alias_method :filter_big_decimal_lt, :filter_lt
|
148
|
+
alias_method :filter_datetime_lt, :filter_lt
|
149
|
+
alias_method :filter_date_lt, :filter_lt
|
141
150
|
|
142
151
|
def filter_lte(scope, attribute, value)
|
143
152
|
column = column_for(scope, attribute)
|
144
153
|
scope.where(column.lteq_any(value))
|
145
154
|
end
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
155
|
+
alias_method :filter_integer_lte, :filter_lte
|
156
|
+
alias_method :filter_float_lte, :filter_lte
|
157
|
+
alias_method :filter_big_decimal_lte, :filter_lte
|
158
|
+
alias_method :filter_date_lte, :filter_lte
|
150
159
|
|
151
160
|
# Ensure fractional seconds don't matter
|
152
161
|
def filter_datetime_eq(scope, attribute, value, is_not: false)
|
153
|
-
ranges = value.map { |v| (v..v + 1.second - 0.00000001) }
|
162
|
+
ranges = value.map { |v| (v..v + 1.second - 0.00000001) unless v.nil? }
|
154
163
|
clause = {attribute => ranges}
|
155
164
|
is_not ? scope.where.not(clause) : scope.where(clause)
|
156
165
|
end
|
@@ -175,14 +184,17 @@ module Graphiti
|
|
175
184
|
end
|
176
185
|
|
177
186
|
# (see Adapters::Abstract#paginate)
|
178
|
-
def paginate(scope, current_page, per_page)
|
179
|
-
scope.page(current_page)
|
187
|
+
def paginate(scope, current_page, per_page, offset)
|
188
|
+
scope = scope.page(current_page) if current_page
|
189
|
+
scope = scope.per(per_page) if per_page
|
190
|
+
scope = scope.padding(offset) if offset
|
191
|
+
scope
|
180
192
|
end
|
181
193
|
|
182
194
|
# (see Adapters::Abstract#count)
|
183
195
|
def count(scope, attr)
|
184
196
|
if attr.to_sym == :total
|
185
|
-
scope.distinct.count
|
197
|
+
scope.distinct.count(:all)
|
186
198
|
else
|
187
199
|
scope.distinct.count(attr)
|
188
200
|
end
|
@@ -231,7 +243,8 @@ module Graphiti
|
|
231
243
|
children.each do |child|
|
232
244
|
if association_type == :many_to_many &&
|
233
245
|
[:create, :update].include?(Graphiti.context[:namespace]) &&
|
234
|
-
!parent.send(association_name).exists?(child.id)
|
246
|
+
!parent.send(association_name).exists?(child.id) &&
|
247
|
+
child.errors.blank?
|
235
248
|
parent.send(association_name) << child
|
236
249
|
else
|
237
250
|
target = association.instance_variable_get(:@target)
|
@@ -273,8 +286,8 @@ module Graphiti
|
|
273
286
|
|
274
287
|
# (see Adapters::Abstract#update)
|
275
288
|
def update(model_class, update_params)
|
276
|
-
instance = model_class.find(update_params.
|
277
|
-
instance.update_attributes(update_params)
|
289
|
+
instance = model_class.find(update_params.only(:id))
|
290
|
+
instance.update_attributes(update_params.except(:id))
|
278
291
|
instance
|
279
292
|
end
|
280
293
|
|
@@ -288,6 +301,18 @@ module Graphiti
|
|
288
301
|
model_instance
|
289
302
|
end
|
290
303
|
|
304
|
+
def close
|
305
|
+
::ActiveRecord::Base.clear_active_connections!
|
306
|
+
end
|
307
|
+
|
308
|
+
def can_group?
|
309
|
+
true
|
310
|
+
end
|
311
|
+
|
312
|
+
def group(scope, attribute)
|
313
|
+
scope.group(attribute)
|
314
|
+
end
|
315
|
+
|
291
316
|
private
|
292
317
|
|
293
318
|
def column_for(scope, name)
|
@@ -298,6 +323,26 @@ module Graphiti
|
|
298
323
|
table[name]
|
299
324
|
end
|
300
325
|
end
|
326
|
+
|
327
|
+
def sanitized_like_for(scope, attribute, value, &block)
|
328
|
+
escape_char = "\\"
|
329
|
+
column = column_for(scope, attribute)
|
330
|
+
map = value.map { |v|
|
331
|
+
v = v.downcase
|
332
|
+
v = Sanitizer.sanitize_like(v, escape_char)
|
333
|
+
block.call v
|
334
|
+
}
|
335
|
+
|
336
|
+
column.lower.matches_any(map, escape_char, true)
|
337
|
+
end
|
338
|
+
|
339
|
+
class Sanitizer
|
340
|
+
extend ::ActiveRecord::Sanitization::ClassMethods
|
341
|
+
|
342
|
+
def self.sanitize_like(*args)
|
343
|
+
sanitize_sql_like(*args)
|
344
|
+
end
|
345
|
+
end
|
301
346
|
end
|
302
347
|
end
|
303
348
|
end
|
@@ -34,7 +34,7 @@ module Graphiti
|
|
34
34
|
|
35
35
|
def build_url(scope)
|
36
36
|
url = resource.remote_url
|
37
|
-
params = scope[:params].merge(scope.except(:params))
|
37
|
+
params = scope[:params].merge(scope.except(:params, :foreign_key))
|
38
38
|
params[:page] ||= {}
|
39
39
|
params[:page][:size] ||= 999
|
40
40
|
params = CGI.unescape(params.to_query)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Adapters
|
3
|
+
module Persistence
|
4
|
+
module Associations
|
5
|
+
def process_belongs_to(persistence, attributes)
|
6
|
+
parents = [].tap do |processed|
|
7
|
+
persistence.iterate(only: [:polymorphic_belongs_to, :belongs_to]) do |x|
|
8
|
+
id = x.dig(:attributes, :id)
|
9
|
+
x[:object] = x[:resource]
|
10
|
+
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
|
11
|
+
processed << x
|
12
|
+
rescue Graphiti::Errors::RecordNotFound
|
13
|
+
if Graphiti.config.raise_on_missing_sidepost
|
14
|
+
path = "relationships/#{x.dig(:meta, :jsonapi_type)}"
|
15
|
+
raise Graphiti::Errors::RecordNotFound.new(x[:sideload].name, id, path)
|
16
|
+
else
|
17
|
+
pointer = "data/relationships/#{x.dig(:meta, :jsonapi_type)}"
|
18
|
+
object = Graphiti::Errors::NullRelation.new(id.to_s, pointer)
|
19
|
+
object.errors.add(:base, :not_found, message: "could not be found")
|
20
|
+
x[:object] = object
|
21
|
+
processed << x
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
update_foreign_key_for_parents(parents, attributes)
|
27
|
+
parents
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_has_many(persistence, caller_model)
|
31
|
+
[].tap do |processed|
|
32
|
+
persistence.iterate(except: [:polymorphic_belongs_to, :belongs_to]) do |x|
|
33
|
+
update_foreign_key(caller_model, x[:attributes], x)
|
34
|
+
|
35
|
+
x[:object] = x[:resource]
|
36
|
+
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships], caller_model, x[:foreign_key])
|
37
|
+
|
38
|
+
processed << x
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_foreign_key_for_parents(parents, attributes)
|
44
|
+
parents.each do |x|
|
45
|
+
update_foreign_key(x[:object], attributes, x)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# The child's attributes should be modified to nil-out the
|
50
|
+
# foreign_key when the parent is being destroyed or disassociated
|
51
|
+
#
|
52
|
+
# This is not the case for HABTM, whose "foreign key" is a join table
|
53
|
+
def update_foreign_key(parent_object, attrs, x)
|
54
|
+
return if x[:sideload].type == :many_to_many
|
55
|
+
|
56
|
+
if [:destroy, :disassociate].include?(x[:meta][:method])
|
57
|
+
if x[:sideload].polymorphic_has_one? || x[:sideload].polymorphic_has_many?
|
58
|
+
attrs[:"#{x[:sideload].polymorphic_as}_type"] = nil
|
59
|
+
end
|
60
|
+
attrs[x[:foreign_key]] = nil
|
61
|
+
update_foreign_type(attrs, x, null: true) if x[:is_polymorphic]
|
62
|
+
else
|
63
|
+
if x[:sideload].polymorphic_has_one? || x[:sideload].polymorphic_has_many?
|
64
|
+
attrs[:"#{x[:sideload].polymorphic_as}_type"] = parent_object.class.name
|
65
|
+
end
|
66
|
+
attrs[x[:foreign_key]] = parent_object.send(x[:primary_key])
|
67
|
+
update_foreign_type(attrs, x) if x[:is_polymorphic]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_foreign_type(attrs, x, null: false)
|
72
|
+
grouping_field = x[:sideload].parent.grouper.field_name
|
73
|
+
attrs[grouping_field] = null ? nil : x[:sideload].group_name
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -14,6 +14,7 @@ module Graphiti
|
|
14
14
|
attr_accessor :pagination_links_on_demand
|
15
15
|
attr_accessor :pagination_links
|
16
16
|
attr_accessor :typecast_reads
|
17
|
+
attr_accessor :raise_on_missing_sidepost
|
17
18
|
|
18
19
|
attr_reader :debug, :debug_models
|
19
20
|
|
@@ -29,6 +30,7 @@ module Graphiti
|
|
29
30
|
@pagination_links_on_demand = false
|
30
31
|
@pagination_links = false
|
31
32
|
@typecast_reads = true
|
33
|
+
@raise_on_missing_sidepost = true
|
32
34
|
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
|
33
35
|
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
|
34
36
|
|
@@ -43,7 +45,7 @@ module Graphiti
|
|
43
45
|
end
|
44
46
|
|
45
47
|
if (logger = ::Rails.logger)
|
46
|
-
self.debug = logger.
|
48
|
+
self.debug = logger.debug? && debug
|
47
49
|
Graphiti.logger = logger
|
48
50
|
end
|
49
51
|
end
|
data/lib/graphiti/debugger.rb
CHANGED
@@ -10,6 +10,8 @@ module Graphiti
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def on_data(name, start, stop, id, payload)
|
13
|
+
return [] unless enabled
|
14
|
+
|
13
15
|
took = ((stop - start) * 1000.0).round(2)
|
14
16
|
params = scrub_params(payload[:params])
|
15
17
|
|
@@ -24,7 +26,7 @@ module Graphiti
|
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
def on_data_exception(payload, params)
|
29
|
+
private def on_data_exception(payload, params)
|
28
30
|
unless payload[:exception_object].instance_variable_get(:@__graphiti_debug)
|
29
31
|
add_chunk do |logs, json|
|
30
32
|
logs << ["\n=== Graphiti Debug ERROR", :red, true]
|
@@ -49,20 +51,20 @@ module Graphiti
|
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
|
-
def results(raw_results)
|
54
|
+
private def results(raw_results)
|
53
55
|
raw_results.map { |r| "[#{r.class.name}, #{r.id.inspect}]" }.join(", ")
|
54
56
|
end
|
55
57
|
|
56
|
-
def on_sideload_data(payload, params, took)
|
58
|
+
private def on_sideload_data(payload, params, took)
|
57
59
|
sideload = payload[:sideload]
|
58
60
|
results = results(payload[:results])
|
59
61
|
add_chunk(payload[:resource], payload[:parent]) do |logs, json|
|
60
62
|
logs << [" \\_ #{sideload.name}", :yellow, true]
|
61
63
|
json[:name] = sideload.name
|
62
|
-
if sideload.class.scope_proc
|
63
|
-
|
64
|
+
query = if sideload.class.scope_proc
|
65
|
+
"#{payload[:resource].class.name}: Manual sideload via .scope"
|
64
66
|
else
|
65
|
-
|
67
|
+
"#{payload[:resource].class.name}.all(#{params.inspect})"
|
66
68
|
end
|
67
69
|
logs << [" #{query}", :cyan, true]
|
68
70
|
json[:query] = query
|
@@ -72,7 +74,7 @@ module Graphiti
|
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
75
|
-
def on_primary_data(payload, params, took)
|
77
|
+
private def on_primary_data(payload, params, took)
|
76
78
|
results = results(payload[:results])
|
77
79
|
add_chunk(payload[:resource], payload[:parent]) do |logs, json|
|
78
80
|
logs << [""]
|
@@ -90,6 +92,8 @@ module Graphiti
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def on_render(name, start, stop, id, payload)
|
95
|
+
return [] unless enabled
|
96
|
+
|
93
97
|
add_chunk do |logs|
|
94
98
|
took = ((stop - start) * 1000.0).round(2)
|
95
99
|
logs << [""]
|
@@ -148,7 +152,7 @@ module Graphiti
|
|
148
152
|
parent: parent,
|
149
153
|
logs: logs,
|
150
154
|
json: json,
|
151
|
-
children: []
|
155
|
+
children: []
|
152
156
|
}
|
153
157
|
end
|
154
158
|
|
@@ -6,32 +6,48 @@ module Graphiti
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def links?
|
9
|
-
@proxy.query.pagination_links?
|
9
|
+
@proxy.query.pagination_links? && @proxy.data.present?
|
10
10
|
end
|
11
11
|
|
12
12
|
def links
|
13
13
|
@links ||= {}.tap do |links|
|
14
|
+
links[:self] = pagination_link(current_page)
|
14
15
|
links[:first] = pagination_link(1)
|
15
16
|
links[:last] = pagination_link(last_page)
|
16
|
-
links[:prev] = pagination_link(current_page - 1)
|
17
|
-
links[:next] = pagination_link(current_page + 1)
|
18
|
-
end.select {|k, v| !v.nil? }
|
17
|
+
links[:prev] = pagination_link(current_page - 1) if has_previous_page?
|
18
|
+
links[:next] = pagination_link(current_page + 1) if has_next_page?
|
19
|
+
end.select { |k, v| !v.nil? }
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_next_page?
|
23
|
+
current_page != last_page && last_page.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_previous_page?
|
27
|
+
current_page != 1 ||
|
28
|
+
!!pagination_params.try(:[], :page).try(:[], :after) ||
|
29
|
+
!!pagination_params.try(:[], :page).try(:[], :offset)
|
19
30
|
end
|
20
31
|
|
21
32
|
private
|
22
33
|
|
34
|
+
def pagination_params
|
35
|
+
@pagination_params ||= @proxy.query.params.reject { |key, _| [:action, :controller, :format].include?(key) }
|
36
|
+
end
|
37
|
+
|
23
38
|
def pagination_link(page)
|
24
39
|
return nil unless @proxy.resource.endpoint
|
25
40
|
|
26
41
|
uri = URI(@proxy.resource.endpoint[:url].to_s)
|
27
42
|
|
43
|
+
page_params = {
|
44
|
+
number: page,
|
45
|
+
size: page_size
|
46
|
+
}
|
47
|
+
page_params[:offset] = offset if offset
|
48
|
+
|
28
49
|
# Overwrite the pagination query params with the desired page
|
29
|
-
uri.query =
|
30
|
-
page: {
|
31
|
-
number: page,
|
32
|
-
size: page_size,
|
33
|
-
},
|
34
|
-
}).to_query
|
50
|
+
uri.query = pagination_params.merge(page: page_params).to_query
|
35
51
|
uri.to_s
|
36
52
|
end
|
37
53
|
|
@@ -41,15 +57,18 @@ module Graphiti
|
|
41
57
|
elsif page_size == 0 || item_count == 0
|
42
58
|
return nil
|
43
59
|
end
|
44
|
-
|
45
|
-
|
60
|
+
|
61
|
+
count = item_count
|
62
|
+
count = item_count - offset if offset
|
63
|
+
@last_page = (count / page_size)
|
64
|
+
@last_page += 1 if count % page_size > 0
|
46
65
|
@last_page
|
47
66
|
end
|
48
67
|
|
49
68
|
def item_count
|
50
69
|
begin
|
51
70
|
return @item_count if @item_count
|
52
|
-
@item_count =
|
71
|
+
@item_count = item_count_from_proxy || item_count_from_stats
|
53
72
|
unless @item_count.is_a?(Numeric)
|
54
73
|
raise TypeError, "#{@proxy.resource}.stat(:total, :count) returned an invalid value #{@item_count}"
|
55
74
|
end
|
@@ -64,10 +83,25 @@ module Graphiti
|
|
64
83
|
@item_count
|
65
84
|
end
|
66
85
|
|
86
|
+
def item_count_from_proxy
|
87
|
+
@proxy.stats.dig(:total, :count)
|
88
|
+
end
|
89
|
+
|
90
|
+
def item_count_from_stats
|
91
|
+
stats = Stats::Payload.new(@proxy.resource, @proxy.query, @proxy.scope.unpaginated_object, @proxy.data)
|
92
|
+
stats.calculate_stat(:total, @proxy.resource.stat(:total, :count))
|
93
|
+
end
|
94
|
+
|
67
95
|
def current_page
|
68
96
|
@current_page ||= (page_param[:number] || 1).to_i
|
69
97
|
end
|
70
98
|
|
99
|
+
def offset
|
100
|
+
@offset ||= if (value = page_param[:offset])
|
101
|
+
value.to_i
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
71
105
|
def page_size
|
72
106
|
@page_size ||= (page_param[:size] ||
|
73
107
|
@proxy.resource.default_page_size ||
|
@@ -87,7 +87,7 @@ class Graphiti::Deserializer
|
|
87
87
|
type: data[:type],
|
88
88
|
temp_id: data[:'temp-id'],
|
89
89
|
method: action,
|
90
|
-
payload_path: ["data"]
|
90
|
+
payload_path: ["data"]
|
91
91
|
}
|
92
92
|
end
|
93
93
|
|
@@ -185,10 +185,10 @@ class Graphiti::Deserializer
|
|
185
185
|
jsonapi_type: datum[:type],
|
186
186
|
temp_id: temp_id,
|
187
187
|
method: method,
|
188
|
-
payload_path: ["included", included_idx]
|
188
|
+
payload_path: ["included", included_idx]
|
189
189
|
},
|
190
190
|
attributes: attributes,
|
191
|
-
relationships: relationships
|
191
|
+
relationships: relationships
|
192
192
|
}
|
193
193
|
end
|
194
194
|
|