graphiti 1.2.15 → 1.2.20
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 +4 -4
- 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 +3 -2
- 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|
|
@@ -58,7 +58,7 @@ module Graphiti
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def resource_for_type(type)
|
61
|
-
resource = children.find { |c| c.type == type }
|
61
|
+
resource = children.find { |c| c.type.to_s == type.to_s }
|
62
62
|
if resource.nil?
|
63
63
|
raise Errors::PolymorphicResourceChildNotFound.new(self, type: type)
|
64
64
|
else
|
@@ -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
|