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
@@ -11,7 +11,7 @@ module Graphiti
|
|
11
11
|
|
12
12
|
# @api private
|
13
13
|
def _all(params, opts, base_scope)
|
14
|
-
runner = Runner.new(self, params, opts.delete(:query))
|
14
|
+
runner = Runner.new(self, params, opts.delete(:query), :all)
|
15
15
|
opts[:params] = params
|
16
16
|
runner.proxy(base_scope, opts)
|
17
17
|
end
|
@@ -27,7 +27,7 @@ module Graphiti
|
|
27
27
|
params[:filter] ||= {}
|
28
28
|
params[:filter][:id] = id if id
|
29
29
|
|
30
|
-
runner = Runner.new(self, params)
|
30
|
+
runner = Runner.new(self, params, nil, :find)
|
31
31
|
runner.proxy base_scope,
|
32
32
|
single: true,
|
33
33
|
raise_on_missing: true,
|
@@ -39,7 +39,7 @@ module Graphiti
|
|
39
39
|
path: path,
|
40
40
|
full_path: full_path_for(path),
|
41
41
|
url: url_for(path),
|
42
|
-
actions: DEFAULT_ACTIONS.dup
|
42
|
+
actions: DEFAULT_ACTIONS.dup
|
43
43
|
}
|
44
44
|
end
|
45
45
|
|
@@ -49,7 +49,7 @@ module Graphiti
|
|
49
49
|
path: path,
|
50
50
|
full_path: full_path_for(path),
|
51
51
|
url: url_for(path),
|
52
|
-
actions: actions
|
52
|
+
actions: actions
|
53
53
|
}
|
54
54
|
end
|
55
55
|
|
@@ -60,7 +60,7 @@ module Graphiti
|
|
60
60
|
path: path,
|
61
61
|
full_path: full_path_for(path),
|
62
62
|
url: url_for(path),
|
63
|
-
actions: actions
|
63
|
+
actions: actions
|
64
64
|
}]
|
65
65
|
end
|
66
66
|
|
@@ -89,7 +89,8 @@ module Graphiti
|
|
89
89
|
|
90
90
|
def update(update_params, meta = nil)
|
91
91
|
model_instance = nil
|
92
|
-
id = update_params
|
92
|
+
id = update_params[:id]
|
93
|
+
update_params = update_params.except(:id)
|
93
94
|
|
94
95
|
run_callbacks :persistence, :update, update_params, meta do
|
95
96
|
run_callbacks :attributes, :update, update_params, meta do |params|
|
@@ -67,7 +67,8 @@ module Graphiti
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def resource_for_model(model)
|
70
|
-
resource = children.find { |c| model.
|
70
|
+
resource = children.find { |c| model.class == c.model } ||
|
71
|
+
children.find { |c| model.is_a?(c.model) }
|
71
72
|
if resource.nil?
|
72
73
|
raise Errors::PolymorphicResourceChildNotFound.new(self, model: model)
|
73
74
|
else
|
data/lib/graphiti/runner.rb
CHANGED
@@ -3,10 +3,11 @@ module Graphiti
|
|
3
3
|
attr_reader :params
|
4
4
|
attr_reader :deserialized_payload
|
5
5
|
|
6
|
-
def initialize(resource_class, params, query = nil)
|
6
|
+
def initialize(resource_class, params, query = nil, action = nil)
|
7
7
|
@resource_class = resource_class
|
8
8
|
@params = params
|
9
9
|
@query = query
|
10
|
+
@action = action
|
10
11
|
|
11
12
|
validator = RequestValidator.new(jsonapi_resource, params)
|
12
13
|
|
@@ -30,7 +31,7 @@ module Graphiti
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def query
|
33
|
-
@query ||= Query.new(jsonapi_resource, params)
|
34
|
+
@query ||= Query.new(jsonapi_resource, params, nil, nil, [], @action)
|
34
35
|
end
|
35
36
|
|
36
37
|
def query_hash
|
@@ -50,7 +51,7 @@ module Graphiti
|
|
50
51
|
def jsonapi_render_options
|
51
52
|
options = {}
|
52
53
|
options.merge!(default_jsonapi_render_options)
|
53
|
-
options[:meta]
|
54
|
+
options[:meta] ||= {}
|
54
55
|
options[:expose] ||= {}
|
55
56
|
options[:expose][:context] = jsonapi_context
|
56
57
|
options
|
data/lib/graphiti/schema.rb
CHANGED
@@ -33,7 +33,7 @@ module Graphiti
|
|
33
33
|
{
|
34
34
|
resources: generate_resources,
|
35
35
|
endpoints: generate_endpoints,
|
36
|
-
types: generate_types
|
36
|
+
types: generate_types
|
37
37
|
}
|
38
38
|
end
|
39
39
|
|
@@ -94,7 +94,7 @@ module Graphiti
|
|
94
94
|
extra_attributes: extra_attributes(r),
|
95
95
|
sorts: sorts(r),
|
96
96
|
filters: filters(r),
|
97
|
-
relationships: relationships(r)
|
97
|
+
relationships: relationships(r)
|
98
98
|
}
|
99
99
|
|
100
100
|
if r.default_sort
|
@@ -121,7 +121,7 @@ module Graphiti
|
|
121
121
|
name: r.name,
|
122
122
|
description: r.description,
|
123
123
|
remote: r.remote_url,
|
124
|
-
relationships: relationships(r)
|
124
|
+
relationships: relationships(r)
|
125
125
|
}
|
126
126
|
}
|
127
127
|
|
@@ -136,7 +136,7 @@ module Graphiti
|
|
136
136
|
type: config[:type].to_s,
|
137
137
|
readable: flag(config[:readable]),
|
138
138
|
writable: flag(config[:writable]),
|
139
|
-
description: resource.attribute_description(name)
|
139
|
+
description: resource.attribute_description(name)
|
140
140
|
}
|
141
141
|
end
|
142
142
|
end
|
@@ -149,7 +149,7 @@ module Graphiti
|
|
149
149
|
attrs[name] = {
|
150
150
|
type: config[:type].to_s,
|
151
151
|
readable: flag(config[:readable]),
|
152
|
-
description: resource.attribute_description(name)
|
152
|
+
description: resource.attribute_description(name)
|
153
153
|
}
|
154
154
|
end
|
155
155
|
end
|
@@ -186,7 +186,7 @@ module Graphiti
|
|
186
186
|
|
187
187
|
config = {
|
188
188
|
type: filter[:type].to_s,
|
189
|
-
operators: filter[:operators].keys.map(&:to_s)
|
189
|
+
operators: filter[:operators].keys.map(&:to_s)
|
190
190
|
}
|
191
191
|
|
192
192
|
config[:single] = true if filter[:single]
|
data/lib/graphiti/scope.rb
CHANGED
@@ -3,10 +3,10 @@ module Graphiti
|
|
3
3
|
attr_accessor :object, :unpaginated_object
|
4
4
|
attr_reader :pagination
|
5
5
|
def initialize(object, resource, query, opts = {})
|
6
|
-
@object
|
7
|
-
@resource
|
8
|
-
@query
|
9
|
-
@opts
|
6
|
+
@object = object
|
7
|
+
@resource = resource
|
8
|
+
@query = query
|
9
|
+
@opts = opts
|
10
10
|
|
11
11
|
@object = @resource.around_scoping(@object, @query.hash) { |scope|
|
12
12
|
apply_scoping(scope, opts)
|
@@ -75,7 +75,7 @@ module Graphiti
|
|
75
75
|
resource: @resource,
|
76
76
|
params: @opts[:params],
|
77
77
|
sideload: @opts[:sideload],
|
78
|
-
parent: @opts[:parent]
|
78
|
+
parent: @opts[:parent]
|
79
79
|
# Set once data is resolved within block
|
80
80
|
# results: ...
|
81
81
|
}
|
@@ -25,9 +25,9 @@ module Graphiti
|
|
25
25
|
# @param [Hash] opts configuration options used by subclasses
|
26
26
|
def initialize(resource, query_hash, scope, opts = {})
|
27
27
|
@query_hash = query_hash
|
28
|
-
@resource
|
29
|
-
@scope
|
30
|
-
@opts
|
28
|
+
@resource = resource
|
29
|
+
@scope = scope
|
30
|
+
@opts = opts
|
31
31
|
end
|
32
32
|
|
33
33
|
# Apply this scoping criteria.
|
@@ -31,7 +31,7 @@ module Graphiti
|
|
31
31
|
|
32
32
|
def filter_via_adapter(filter, operator, value)
|
33
33
|
type_name = Types.name_for(filter.values.first[:type])
|
34
|
-
method
|
34
|
+
method = :"filter_#{type_name}_#{operator}"
|
35
35
|
attribute = filter.keys.first
|
36
36
|
|
37
37
|
if resource.adapter.respond_to?(method)
|
@@ -54,6 +54,8 @@ module Graphiti
|
|
54
54
|
unless type[:canonical_name] == :hash || !value.is_a?(String)
|
55
55
|
value = parse_string_value(filter.values[0], value)
|
56
56
|
end
|
57
|
+
|
58
|
+
check_deny_empty_filters!(resource, filter, value)
|
57
59
|
value = parse_string_null(filter.values[0], value)
|
58
60
|
validate_singular(resource, filter, value)
|
59
61
|
value = coerce_types(filter.values[0], param_name.to_sym, value)
|
@@ -82,11 +84,11 @@ module Graphiti
|
|
82
84
|
type = Types[filter.values[0][:type]][:canonical_name]
|
83
85
|
if param_value.is_a?(Hash) && type == :hash
|
84
86
|
operators_keys = filter.values[0][:operators].keys
|
85
|
-
unless param_value.keys.all? {|k| operators_keys.include?(k)}
|
86
|
-
param_value = {
|
87
|
+
unless param_value.keys.all? { |k| operators_keys.include?(k) }
|
88
|
+
param_value = {eq: param_value}
|
87
89
|
end
|
88
90
|
elsif !param_value.is_a?(Hash) || param_value.empty?
|
89
|
-
param_value = {
|
91
|
+
param_value = {eq: param_value}
|
90
92
|
end
|
91
93
|
|
92
94
|
param_value.map do |operator, value|
|
@@ -184,10 +186,10 @@ module Graphiti
|
|
184
186
|
# remove the quote characters from the quoted strings
|
185
187
|
quotes.each { |q| q.gsub!("{{", "").gsub!("}}", "") }
|
186
188
|
# merge everything back together into an array
|
187
|
-
if singular_filter
|
188
|
-
|
189
|
+
value = if singular_filter
|
190
|
+
Array(value) + quotes
|
189
191
|
else
|
190
|
-
|
192
|
+
Array(value.split(",")) + quotes
|
191
193
|
end
|
192
194
|
# remove any blanks that are left
|
193
195
|
value.reject! { |v| v.length.zero? }
|
@@ -200,5 +202,13 @@ module Graphiti
|
|
200
202
|
|
201
203
|
value
|
202
204
|
end
|
205
|
+
|
206
|
+
def check_deny_empty_filters!(resource, filter, value)
|
207
|
+
return unless filter.values[0][:deny_empty]
|
208
|
+
|
209
|
+
if value.nil? || value.empty? || value == "null"
|
210
|
+
raise Errors::InvalidFilterValue.new(resource, filter, "(empty)")
|
211
|
+
end
|
212
|
+
end
|
203
213
|
end
|
204
214
|
end
|
data/lib/graphiti/serializer.rb
CHANGED
@@ -25,6 +25,7 @@ module Graphiti
|
|
25
25
|
def as_jsonapi(*)
|
26
26
|
super.tap do |hash|
|
27
27
|
strip_relationships!(hash) if strip_relationships?
|
28
|
+
add_links!(hash)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -49,6 +50,12 @@ module Graphiti
|
|
49
50
|
|
50
51
|
private
|
51
52
|
|
53
|
+
def add_links!(hash)
|
54
|
+
return unless @resource.respond_to?(:links?)
|
55
|
+
|
56
|
+
hash[:links] = @resource.links(@object) if @resource.links?
|
57
|
+
end
|
58
|
+
|
52
59
|
def strip_relationships!(hash)
|
53
60
|
hash[:relationships]&.select! do |name, payload|
|
54
61
|
payload.key?(:data)
|
data/lib/graphiti/sideload.rb
CHANGED
@@ -18,31 +18,31 @@ module Graphiti
|
|
18
18
|
:link_proc
|
19
19
|
|
20
20
|
def initialize(name, opts)
|
21
|
-
@name
|
21
|
+
@name = name
|
22
22
|
validate_options!(opts)
|
23
|
-
@parent_resource_class
|
24
|
-
@resource_class
|
25
|
-
@primary_key
|
26
|
-
@foreign_key
|
27
|
-
@type
|
28
|
-
@base_scope
|
29
|
-
@readable
|
30
|
-
@writable
|
31
|
-
@as
|
32
|
-
@link
|
33
|
-
@single
|
34
|
-
@remote
|
23
|
+
@parent_resource_class = opts[:parent_resource]
|
24
|
+
@resource_class = opts[:resource]
|
25
|
+
@primary_key = opts[:primary_key]
|
26
|
+
@foreign_key = opts[:foreign_key]
|
27
|
+
@type = opts[:type]
|
28
|
+
@base_scope = opts[:base_scope]
|
29
|
+
@readable = evaluate_flag(opts[:readable])
|
30
|
+
@writable = evaluate_flag(opts[:writable])
|
31
|
+
@as = opts[:as]
|
32
|
+
@link = opts[:link]
|
33
|
+
@single = opts[:single]
|
34
|
+
@remote = opts[:remote]
|
35
35
|
apply_belongs_to_many_filter if type == :many_to_many
|
36
36
|
|
37
|
-
@description
|
37
|
+
@description = opts[:description]
|
38
38
|
|
39
39
|
# polymorphic has_many
|
40
|
-
@polymorphic_as
|
40
|
+
@polymorphic_as = opts[:polymorphic_as]
|
41
41
|
# polymorphic_belongs_to-specific
|
42
|
-
@group_name
|
43
|
-
@polymorphic_child
|
44
|
-
@parent
|
45
|
-
@always_include_resource_ids
|
42
|
+
@group_name = opts[:group_name]
|
43
|
+
@polymorphic_child = opts[:polymorphic_child]
|
44
|
+
@parent = opts[:parent]
|
45
|
+
@always_include_resource_ids = opts[:always_include_resource_ids]
|
46
46
|
|
47
47
|
if polymorphic_child?
|
48
48
|
parent.resource.polymorphic << resource_class
|
@@ -132,6 +132,10 @@ module Graphiti
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
+
def link_filter(parents)
|
136
|
+
base_filter(parents)
|
137
|
+
end
|
138
|
+
|
135
139
|
# The parent resource is a remote,
|
136
140
|
# AND the sideload is a remote to the same endpoint
|
137
141
|
def shared_remote?
|
@@ -197,7 +201,7 @@ module Graphiti
|
|
197
201
|
|
198
202
|
with_error_handling Errors::SideloadParamsError do
|
199
203
|
params = load_params(parents, query)
|
200
|
-
params_proc&.call(params, parents)
|
204
|
+
params_proc&.call(params, parents, context)
|
201
205
|
return [] if blank_query?(params)
|
202
206
|
opts = load_options(parents, query)
|
203
207
|
opts[:sideload] = self
|
@@ -426,13 +430,17 @@ module Graphiti
|
|
426
430
|
return false if flag.blank?
|
427
431
|
|
428
432
|
case flag.class.name
|
429
|
-
when "Symbol","String"
|
433
|
+
when "Symbol", "String"
|
430
434
|
resource.send(flag)
|
431
435
|
when "Proc"
|
432
|
-
|
436
|
+
resource.instance_exec(&flag)
|
433
437
|
else
|
434
438
|
!!flag
|
435
439
|
end
|
436
440
|
end
|
441
|
+
|
442
|
+
def context
|
443
|
+
Graphiti.context[:object]
|
444
|
+
end
|
437
445
|
end
|
438
446
|
end
|
@@ -1,8 +1,18 @@
|
|
1
1
|
class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
2
|
+
def initialize(name, opts)
|
3
|
+
@inverse_filter = opts[:inverse_filter]
|
4
|
+
|
5
|
+
super(name, opts)
|
6
|
+
end
|
7
|
+
|
2
8
|
def type
|
3
9
|
:has_many
|
4
10
|
end
|
5
11
|
|
12
|
+
def inverse_filter
|
13
|
+
@inverse_filter || foreign_key
|
14
|
+
end
|
15
|
+
|
6
16
|
def load_params(parents, query)
|
7
17
|
query.hash.tap do |hash|
|
8
18
|
hash[:filter] ||= {}
|
@@ -11,11 +21,19 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
|
11
21
|
end
|
12
22
|
|
13
23
|
def base_filter(parents)
|
14
|
-
{foreign_key =>
|
24
|
+
{foreign_key => parent_filter(parents)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def link_filter(parents)
|
28
|
+
{inverse_filter => parent_filter(parents)}
|
15
29
|
end
|
16
30
|
|
17
31
|
private
|
18
32
|
|
33
|
+
def parent_filter(parents)
|
34
|
+
ids_for_parents(parents).join(",")
|
35
|
+
end
|
36
|
+
|
19
37
|
def child_map(children)
|
20
38
|
children.group_by(&foreign_key)
|
21
39
|
end
|
@@ -11,8 +11,12 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
|
|
11
11
|
foreign_key.values.first
|
12
12
|
end
|
13
13
|
|
14
|
+
def inverse_filter
|
15
|
+
@inverse_filter || true_foreign_key
|
16
|
+
end
|
17
|
+
|
14
18
|
def base_filter(parents)
|
15
|
-
{true_foreign_key =>
|
19
|
+
{true_foreign_key => parent_filter(parents)}
|
16
20
|
end
|
17
21
|
|
18
22
|
def infer_foreign_key
|
@@ -32,7 +36,7 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
|
|
32
36
|
self_ref = self
|
33
37
|
fk_type = parent_resource_class.attributes[:id][:type]
|
34
38
|
fk_type = :hash if polymorphic?
|
35
|
-
resource_class.filter
|
39
|
+
resource_class.filter inverse_filter, fk_type do
|
36
40
|
eq do |scope, value|
|
37
41
|
self_ref.belongs_to_many_filter(scope, value)
|
38
42
|
end
|