graphiti 1.2.16 → 1.2.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +43 -14
- data/Appraisals +37 -5
- data/CHANGELOG.md +1 -1
- data/Gemfile +2 -0
- data/Guardfile +5 -5
- data/deprecated_generators/graphiti/resource_generator.rb +1 -1
- data/gemfiles/rails_5_0.gemfile +18 -0
- data/gemfiles/rails_5_0_graphiti_rails.gemfile +20 -0
- data/gemfiles/rails_5_1.gemfile +18 -0
- data/gemfiles/rails_5_1_graphiti_rails.gemfile +20 -0
- data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +0 -0
- data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +0 -0
- data/gemfiles/rails_6.gemfile +1 -1
- data/gemfiles/rails_6_graphiti_rails.gemfile +1 -1
- data/graphiti.gemspec +11 -12
- data/lib/graphiti.rb +3 -3
- data/lib/graphiti/adapters/abstract.rb +3 -3
- data/lib/graphiti/adapters/active_record.rb +65 -36
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +2 -1
- data/lib/graphiti/configuration.rb +1 -1
- data/lib/graphiti/debugger.rb +12 -8
- data/lib/graphiti/delegates/pagination.rb +8 -4
- data/lib/graphiti/deserializer.rb +3 -3
- data/lib/graphiti/errors.rb +24 -4
- data/lib/graphiti/extensions/extra_attribute.rb +1 -1
- data/lib/graphiti/query.rb +32 -16
- data/lib/graphiti/railtie.rb +1 -1
- data/lib/graphiti/request_validator.rb +4 -4
- data/lib/graphiti/request_validators/update_validator.rb +1 -2
- data/lib/graphiti/request_validators/validator.rb +2 -2
- data/lib/graphiti/resource.rb +10 -0
- data/lib/graphiti/resource/configuration.rb +10 -3
- data/lib/graphiti/resource/dsl.rb +10 -4
- data/lib/graphiti/resource/interface.rb +2 -2
- data/lib/graphiti/resource/links.rb +3 -3
- data/lib/graphiti/resource/persistence.rb +2 -1
- data/lib/graphiti/resource/polymorphism.rb +2 -1
- data/lib/graphiti/resource/remote.rb +1 -1
- data/lib/graphiti/runner.rb +4 -3
- data/lib/graphiti/schema.rb +6 -6
- data/lib/graphiti/scope.rb +5 -5
- data/lib/graphiti/scoping/base.rb +3 -3
- data/lib/graphiti/scoping/filter.rb +17 -7
- data/lib/graphiti/scoping/sort.rb +1 -1
- data/lib/graphiti/serializer.rb +7 -0
- data/lib/graphiti/sideload.rb +30 -22
- 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 +6 -2
- data/lib/graphiti/stats/payload.rb +4 -4
- data/lib/graphiti/types.rb +15 -15
- data/lib/graphiti/util/link.rb +6 -2
- data/lib/graphiti/util/persistence.rb +16 -10
- data/lib/graphiti/util/relationship_payload.rb +4 -4
- data/lib/graphiti/util/simple_errors.rb +1 -1
- data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
- data/lib/graphiti/version.rb +1 -1
- metadata +18 -22
@@ -36,7 +36,8 @@ class Graphiti::Adapters::ActiveRecord::ManyToManySideload < Graphiti::Sideload:
|
|
36
36
|
|
37
37
|
def filter_for(scope, value, type = nil)
|
38
38
|
scope
|
39
|
-
.
|
39
|
+
.preload(through_relationship_name)
|
40
|
+
.joins(through_relationship_name)
|
40
41
|
.where(belongs_to_many_clause(value, type))
|
41
42
|
end
|
42
43
|
|
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
|
|
@@ -15,22 +15,26 @@ module Graphiti
|
|
15
15
|
links[:last] = pagination_link(last_page)
|
16
16
|
links[:prev] = pagination_link(current_page - 1) unless current_page == 1
|
17
17
|
links[:next] = pagination_link(current_page + 1) unless current_page == last_page
|
18
|
-
end.select {|k, v| !v.nil? }
|
18
|
+
end.select { |k, v| !v.nil? }
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def pagination_params
|
24
|
+
@pagination_params ||= @proxy.query.params.reject { |key, _| [:action, :controller, :format].include?(key) }
|
25
|
+
end
|
26
|
+
|
23
27
|
def pagination_link(page)
|
24
28
|
return nil unless @proxy.resource.endpoint
|
25
29
|
|
26
30
|
uri = URI(@proxy.resource.endpoint[:url].to_s)
|
27
31
|
|
28
32
|
# Overwrite the pagination query params with the desired page
|
29
|
-
uri.query =
|
33
|
+
uri.query = pagination_params.merge({
|
30
34
|
page: {
|
31
35
|
number: page,
|
32
|
-
size: page_size
|
33
|
-
}
|
36
|
+
size: page_size
|
37
|
+
}
|
34
38
|
}).to_query
|
35
39
|
uri.to_s
|
36
40
|
end
|
@@ -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
|
|
data/lib/graphiti/errors.rb
CHANGED
@@ -143,8 +143,13 @@ module Graphiti
|
|
143
143
|
def message
|
144
144
|
allow = @filter.values[0][:allow]
|
145
145
|
deny = @filter.values[0][:deny]
|
146
|
+
value_string = if @value == "(empty)"
|
147
|
+
"empty value"
|
148
|
+
else
|
149
|
+
"value #{@value.inspect}"
|
150
|
+
end
|
146
151
|
msg = <<-MSG
|
147
|
-
#{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid
|
152
|
+
#{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid #{value_string}.
|
148
153
|
MSG
|
149
154
|
msg << "\nAllowlist: #{allow.inspect}" if allow
|
150
155
|
msg << "\nDenylist: #{deny.inspect}" if deny
|
@@ -189,7 +194,7 @@ module Graphiti
|
|
189
194
|
|
190
195
|
Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
|
191
196
|
|
192
|
-
If you do not wish to generate a link, pass link: false or set self.
|
197
|
+
If you do not wish to generate a link, pass link: false or set self.autolink = false.
|
193
198
|
MSG
|
194
199
|
end
|
195
200
|
end
|
@@ -316,14 +321,14 @@ module Graphiti
|
|
316
321
|
sortable: "sort on",
|
317
322
|
filterable: "filter on",
|
318
323
|
readable: "read",
|
319
|
-
writable: "write"
|
324
|
+
writable: "write"
|
320
325
|
}[@flag]
|
321
326
|
else
|
322
327
|
{
|
323
328
|
sortable: "add sort",
|
324
329
|
filterable: "add filter",
|
325
330
|
readable: "read",
|
326
|
-
writable: "write"
|
331
|
+
writable: "write"
|
327
332
|
}[@flag]
|
328
333
|
end
|
329
334
|
end
|
@@ -722,6 +727,21 @@ module Graphiti
|
|
722
727
|
end
|
723
728
|
|
724
729
|
class RecordNotFound < Base
|
730
|
+
def initialize(resource = nil, id = nil, path = nil)
|
731
|
+
@resource = resource
|
732
|
+
@id = id
|
733
|
+
@path = path
|
734
|
+
end
|
735
|
+
|
736
|
+
def message
|
737
|
+
if !@resource.nil? && !@id.nil?
|
738
|
+
"The referenced resource '#{@resource}' with id '#{@id}' could not be found.".tap do |msg|
|
739
|
+
msg << " Referenced at '#{@path}'" unless @path.nil?
|
740
|
+
end
|
741
|
+
else
|
742
|
+
"Specified Record Not Found"
|
743
|
+
end
|
744
|
+
end
|
725
745
|
end
|
726
746
|
|
727
747
|
class RequiredFilter < Base
|
data/lib/graphiti/query.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class Query
|
3
|
-
attr_reader :resource, :association_name, :params
|
3
|
+
attr_reader :resource, :association_name, :params, :action
|
4
4
|
|
5
|
-
def initialize(resource, params, association_name = nil, nested_include = nil, parents = [])
|
5
|
+
def initialize(resource, params, association_name = nil, nested_include = nil, parents = [], action = nil)
|
6
6
|
@resource = resource
|
7
7
|
@association_name = association_name
|
8
8
|
@params = params
|
@@ -11,6 +11,7 @@ module Graphiti
|
|
11
11
|
@params = @params.deep_symbolize_keys
|
12
12
|
@include_param = nested_include || @params[:include]
|
13
13
|
@parents = parents
|
14
|
+
@action = parse_action(action)
|
14
15
|
end
|
15
16
|
|
16
17
|
def association?
|
@@ -44,16 +45,19 @@ module Graphiti
|
|
44
45
|
|
45
46
|
def hash
|
46
47
|
@hash ||= {}.tap do |h|
|
47
|
-
h[:filter] = filters
|
48
|
-
h[:sort] = sorts
|
49
|
-
h[:page] = pagination
|
50
|
-
|
51
|
-
|
52
|
-
h[:extra_fields] = extra_fields
|
48
|
+
h[:filter] = filters
|
49
|
+
h[:sort] = sorts
|
50
|
+
h[:page] = pagination
|
51
|
+
if association?
|
52
|
+
resource_type = @resource.class.type
|
53
|
+
h[:extra_fields] = {resource_type => extra_fields[resource_type]} if extra_fields.key?(resource_type)
|
54
|
+
else
|
55
|
+
h[:fields] = fields
|
56
|
+
h[:extra_fields] = extra_fields
|
53
57
|
end
|
54
|
-
h[:stats] = stats
|
55
|
-
h[:include] = sideload_hash
|
56
|
-
end
|
58
|
+
h[:stats] = stats
|
59
|
+
h[:include] = sideload_hash
|
60
|
+
end.reject { |_, value| value.empty? }
|
57
61
|
end
|
58
62
|
|
59
63
|
def zero_results?
|
@@ -92,7 +96,7 @@ module Graphiti
|
|
92
96
|
sl_resource = resource_for_sideload(sideload)
|
93
97
|
query_parents = parents + [self]
|
94
98
|
sub_hash = sub_hash[:include] if sub_hash.key?(:include)
|
95
|
-
hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents)
|
99
|
+
hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents, :all)
|
96
100
|
else
|
97
101
|
handle_missing_sideload(key)
|
98
102
|
end
|
@@ -232,7 +236,7 @@ module Graphiti
|
|
232
236
|
return true if @resource.remote?
|
233
237
|
|
234
238
|
if (att = @resource.get_attr(name, flag, request: true))
|
235
|
-
|
239
|
+
att
|
236
240
|
else
|
237
241
|
not_associated_name = !@resource.class.association_names.include?(name)
|
238
242
|
not_associated_type = !@resource.class.association_types.include?(name)
|
@@ -262,8 +266,8 @@ module Graphiti
|
|
262
266
|
def parse_fieldset(fieldset)
|
263
267
|
{}.tap do |hash|
|
264
268
|
fieldset.each_pair do |type, fields|
|
265
|
-
type
|
266
|
-
fields
|
269
|
+
type = type.to_sym
|
270
|
+
fields = fields.split(",") unless fields.is_a?(Array)
|
267
271
|
hash[type] = fields.map(&:to_sym)
|
268
272
|
end
|
269
273
|
end
|
@@ -282,7 +286,7 @@ module Graphiti
|
|
282
286
|
|
283
287
|
def sort_hash(attr)
|
284
288
|
value = attr[0] == "-" ? :desc : :asc
|
285
|
-
key
|
289
|
+
key = attr.sub("-", "").to_sym
|
286
290
|
|
287
291
|
{key => value}
|
288
292
|
end
|
@@ -312,5 +316,17 @@ module Graphiti
|
|
312
316
|
end
|
313
317
|
end
|
314
318
|
end
|
319
|
+
|
320
|
+
def parse_action(action)
|
321
|
+
action ||= @params.fetch(:action, Graphiti.context[:namespace]).try(:to_sym)
|
322
|
+
case action
|
323
|
+
when :index
|
324
|
+
:all
|
325
|
+
when :show
|
326
|
+
:find
|
327
|
+
else
|
328
|
+
action
|
329
|
+
end
|
330
|
+
end
|
315
331
|
end
|
316
332
|
end
|
data/lib/graphiti/railtie.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class RequestValidator
|
3
3
|
delegate :validate,
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
:validate!,
|
5
|
+
:errors,
|
6
|
+
:deserialized_payload,
|
7
|
+
to: :@validator
|
8
8
|
|
9
9
|
def initialize(root_resource, raw_params)
|
10
10
|
@validator = ValidatorFactory.create(root_resource, raw_params)
|
@@ -5,7 +5,7 @@ module Graphiti
|
|
5
5
|
if required_payload? && payload_matches_endpoint?
|
6
6
|
super
|
7
7
|
else
|
8
|
-
|
8
|
+
false
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -36,7 +36,6 @@ module Graphiti
|
|
36
36
|
attribute_mismatch([:data, :id])
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
39
|
meta_type = @raw_params.dig(:data, :type)
|
41
40
|
|
42
41
|
# NOTE: calling #to_s and comparing 2 strings is slower than
|
@@ -25,7 +25,7 @@ module Graphiti
|
|
25
25
|
|
26
26
|
def validate!
|
27
27
|
unless validate
|
28
|
-
raise @error_class || Graphiti::Errors::InvalidRequest,
|
28
|
+
raise @error_class || Graphiti::Errors::InvalidRequest, errors
|
29
29
|
end
|
30
30
|
|
31
31
|
true
|
@@ -47,7 +47,7 @@ module Graphiti
|
|
47
47
|
def process_relationships(resource, relationships, payload_path)
|
48
48
|
opts = {
|
49
49
|
resource: resource,
|
50
|
-
relationships: relationships
|
50
|
+
relationships: relationships
|
51
51
|
}
|
52
52
|
|
53
53
|
Graphiti::Util::RelationshipPayload.iterate(opts) do |x|
|
data/lib/graphiti/resource.rb
CHANGED
@@ -148,5 +148,15 @@ module Graphiti
|
|
148
148
|
end
|
149
149
|
response
|
150
150
|
end
|
151
|
+
|
152
|
+
def links?
|
153
|
+
self.class.links.any?
|
154
|
+
end
|
155
|
+
|
156
|
+
def links(model)
|
157
|
+
self.class.links.each_with_object({}) do |(name, blk), memo|
|
158
|
+
memo[name] = instance_exec(model, &blk)
|
159
|
+
end
|
160
|
+
end
|
151
161
|
end
|
152
162
|
end
|
@@ -22,7 +22,7 @@ module Graphiti
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def type=(val)
|
25
|
-
val = val
|
25
|
+
val = val&.to_sym
|
26
26
|
if (val = super)
|
27
27
|
serializer.type(val)
|
28
28
|
end
|
@@ -42,7 +42,7 @@ module Graphiti
|
|
42
42
|
path: val,
|
43
43
|
full_path: val,
|
44
44
|
url: val,
|
45
|
-
actions: [:index, :show]
|
45
|
+
actions: [:index, :show]
|
46
46
|
}
|
47
47
|
end
|
48
48
|
|
@@ -82,7 +82,8 @@ module Graphiti
|
|
82
82
|
:attributes_schema_by_default,
|
83
83
|
:relationships_readable_by_default,
|
84
84
|
:relationships_writable_by_default,
|
85
|
-
:filters_accept_nil_by_default
|
85
|
+
:filters_accept_nil_by_default,
|
86
|
+
:filters_deny_empty_by_default
|
86
87
|
|
87
88
|
class << self
|
88
89
|
prepend Overrides
|
@@ -104,6 +105,7 @@ module Graphiti
|
|
104
105
|
default(klass, :relationships_readable_by_default, true)
|
105
106
|
default(klass, :relationships_writable_by_default, true)
|
106
107
|
default(klass, :filters_accept_nil_by_default, false)
|
108
|
+
default(klass, :filters_deny_empty_by_default, false)
|
107
109
|
|
108
110
|
unless klass.config[:attributes][:id]
|
109
111
|
klass.attribute :id, :integer_id
|
@@ -198,6 +200,7 @@ module Graphiti
|
|
198
200
|
extra_attributes: {},
|
199
201
|
sideloads: {},
|
200
202
|
callbacks: {},
|
203
|
+
links: {}
|
201
204
|
}
|
202
205
|
end
|
203
206
|
|
@@ -236,6 +239,10 @@ module Graphiti
|
|
236
239
|
def default_filters
|
237
240
|
config[:default_filters]
|
238
241
|
end
|
242
|
+
|
243
|
+
def links
|
244
|
+
config[:links]
|
245
|
+
end
|
239
246
|
end
|
240
247
|
|
241
248
|
def get_attr!(name, flag, options = {})
|
@@ -32,7 +32,8 @@ module Graphiti
|
|
32
32
|
dependencies: opts[:dependent],
|
33
33
|
required: required,
|
34
34
|
operators: operators.to_hash,
|
35
|
-
allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default)
|
35
|
+
allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
|
36
|
+
deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
|
36
37
|
}
|
37
38
|
elsif (type = args[0])
|
38
39
|
attribute name, type, only: [:filterable], allow: opts[:allow]
|
@@ -55,7 +56,7 @@ module Graphiti
|
|
55
56
|
|
56
57
|
if get_attr(name, :sortable, raise_error: :only_unsupported)
|
57
58
|
config[:sorts][name] = {
|
58
|
-
proc: blk
|
59
|
+
proc: blk
|
59
60
|
}.merge(opts.slice(:only))
|
60
61
|
elsif (type = args[0])
|
61
62
|
attribute name, type, only: [:sortable]
|
@@ -78,7 +79,7 @@ module Graphiti
|
|
78
79
|
def default_filter(name = nil, &blk)
|
79
80
|
name ||= :__default
|
80
81
|
config[:default_filters][name.to_sym] = {
|
81
|
-
filter: blk
|
82
|
+
filter: blk
|
82
83
|
}
|
83
84
|
end
|
84
85
|
|
@@ -131,9 +132,10 @@ module Graphiti
|
|
131
132
|
readable: true,
|
132
133
|
writable: false,
|
133
134
|
sortable: false,
|
134
|
-
filterable: false
|
135
|
+
filterable: false
|
135
136
|
}
|
136
137
|
options = defaults.merge(options)
|
138
|
+
attribute_option(options, :readable)
|
137
139
|
config[:extra_attributes][name] = options
|
138
140
|
apply_extra_attributes_to_serializer
|
139
141
|
end
|
@@ -146,6 +148,10 @@ module Graphiti
|
|
146
148
|
end
|
147
149
|
end
|
148
150
|
|
151
|
+
def link(name, &blk)
|
152
|
+
config[:links][name.to_sym] = blk
|
153
|
+
end
|
154
|
+
|
149
155
|
def all_attributes
|
150
156
|
attributes.merge(extra_attributes)
|
151
157
|
end
|