graphiti 1.2.16 → 1.2.21
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/.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
|