graphiti 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/CHANGELOG.md +17 -6
- data/graphiti.gemspec +1 -1
- data/lib/generators/graphiti/templates/create_request_spec.rb.erb +1 -2
- data/lib/generators/graphiti/templates/resource_writes_spec.rb.erb +1 -2
- data/lib/graphiti.rb +2 -8
- data/lib/graphiti/deserializer.rb +4 -0
- data/lib/graphiti/errors.rb +44 -12
- data/lib/graphiti/railtie.rb +1 -1
- data/lib/graphiti/request_validator.rb +94 -0
- data/lib/graphiti/resource.rb +1 -1
- data/lib/graphiti/runner.rb +51 -1
- data/lib/graphiti/util/attribute_check.rb +4 -4
- data/lib/graphiti/util/link.rb +2 -2
- data/lib/graphiti/util/persistence.rb +4 -28
- data/lib/graphiti/util/relationship_payload.rb +2 -1
- data/lib/graphiti/util/serializer_attributes.rb +1 -1
- data/lib/graphiti/util/simple_errors.rb +87 -0
- data/lib/graphiti/version.rb +1 -1
- metadata +7 -6
- data/lib/graphiti/base.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 292ab84397ae7ce761553abe89c12027aa9df8eaf42e19c746b3e15b483b129f
|
4
|
+
data.tar.gz: f0ed1abbace00c61d43821d8b8a8a59365e62f5fd2065d356391a8e391bd483b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 707372b30ade3c299f8569de0e53351cbca316f93e0b04aa4a0fbcac165c164c12cf3ec6ca57e06e212f616765031cdb4643a33cd941871ae5fd5e590bf5a00d
|
7
|
+
data.tar.gz: 29b33d2e5667cc0ec0a48883e1138312756fd0b1bafa22007bb1dd3a0fafe1567a3c6f7b0709e60d4f09cd8bdb4c8abd8578cd987d50c3128295971be94035b1
|
data/.travis.yml
CHANGED
@@ -37,9 +37,9 @@ matrix:
|
|
37
37
|
rvm: 2.6
|
38
38
|
|
39
39
|
script:
|
40
|
-
- bundle exec $COMMAND
|
40
|
+
- bundle _1.17.3_ exec $COMMAND
|
41
41
|
|
42
|
-
install: bundle install --retry=3 --jobs=3
|
42
|
+
install: bundle _1.17.3_ install --retry=3 --jobs=3
|
43
43
|
before_install:
|
44
44
|
- gem install bundler -v '1.17.3'
|
45
45
|
deploy:
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,25 @@
|
|
1
|
+
## 1.1.0
|
2
|
+
|
3
|
+
Features:
|
4
|
+
|
5
|
+
- [#126](https://github.com/graphiti-api/graphiti/pull/126) Render helpful user-facing errors when a write payload is invalid (@wadetandy)
|
6
|
+
|
7
|
+
Fixes:
|
8
|
+
|
9
|
+
- [#136](https://github.com/graphiti-api/graphiti/pull/136) Fix remote
|
10
|
+
belongs_to links (@richmolj)
|
11
|
+
|
12
|
+
Misc:
|
13
|
+
|
14
|
+
- [#123](https://github.com/graphiti-api/graphiti/pull/123) Throw
|
15
|
+
better error when polymorphic type not found.
|
16
|
+
|
1
17
|
## 1.0.3
|
2
18
|
|
3
19
|
Fixes:
|
4
20
|
|
5
21
|
- [#130](https://github.com/graphiti-api/graphiti/pull/130) Run query
|
6
|
-
blocks in resource context
|
22
|
+
blocks in resource context (@richmolj)
|
7
23
|
|
8
24
|
## 1.0.2
|
9
25
|
|
@@ -20,11 +36,6 @@ Fixes:
|
|
20
36
|
|
21
37
|
### master (unreleased)
|
22
38
|
|
23
|
-
Misc:
|
24
|
-
|
25
|
-
- [#123](https://github.com/graphiti-api/graphiti/pull/123) Throw
|
26
|
-
better error when polymorphic type not found.
|
27
|
-
|
28
39
|
<!-- ### [version (YYYY-MM-DD)](diff_link) -->
|
29
40
|
<!-- Breaking changes:-->
|
30
41
|
<!-- Features:-->
|
data/graphiti.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "jsonapi-serializable", "~> 0.3.0"
|
22
22
|
spec.add_dependency "dry-types", "~> 0.15"
|
23
|
-
spec.add_dependency "graphiti_errors", "~> 1.0
|
23
|
+
spec.add_dependency "graphiti_errors", "~> 1.1.0"
|
24
24
|
spec.add_dependency "concurrent-ruby", "~> 1.0"
|
25
25
|
spec.add_dependency "activesupport", [">= 4.1", "< 6"]
|
26
26
|
|
@@ -8,8 +8,7 @@ RSpec.describe "<%= type %>#create", type: :request do
|
|
8
8
|
describe 'basic create' do
|
9
9
|
let(:params) do
|
10
10
|
<%- if defined?(FactoryBot) -%>
|
11
|
-
attributes_for(:<%= type.to_s.singularize %>)
|
12
|
-
except("created_at", "updated_at")
|
11
|
+
attributes_for(:<%= type.to_s.singularize %>)
|
13
12
|
<%- else -%>
|
14
13
|
{
|
15
14
|
# ... your attrs here
|
@@ -7,8 +7,7 @@ RSpec.describe <%= resource_class %>, type: :resource do
|
|
7
7
|
data: {
|
8
8
|
type: '<%= type %>',
|
9
9
|
<%- if defined?(FactoryBot) -%>
|
10
|
-
attributes: attributes_for(:<%= type.to_s.singularize %>)
|
11
|
-
except("created_at", "updated_at")
|
10
|
+
attributes: attributes_for(:<%= type.to_s.singularize %>)
|
12
11
|
<%- else -%>
|
13
12
|
attributes: { }
|
14
13
|
<%- end -%>
|
data/lib/graphiti.rb
CHANGED
@@ -38,6 +38,7 @@ require "graphiti/sideload/many_to_many"
|
|
38
38
|
require "graphiti/sideload/polymorphic_belongs_to"
|
39
39
|
require "graphiti/resource"
|
40
40
|
require "graphiti/resource_proxy"
|
41
|
+
require "graphiti/request_validator"
|
41
42
|
require "graphiti/query"
|
42
43
|
require "graphiti/scope"
|
43
44
|
require "graphiti/deserializer"
|
@@ -61,6 +62,7 @@ require "graphiti/util/relationship_payload"
|
|
61
62
|
require "graphiti/util/persistence"
|
62
63
|
require "graphiti/util/validation_response"
|
63
64
|
require "graphiti/util/sideload"
|
65
|
+
require "graphiti/util/simple_errors"
|
64
66
|
require "graphiti/util/transaction_hooks_recorder"
|
65
67
|
require "graphiti/util/attribute_check"
|
66
68
|
require "graphiti/util/serializer_attributes"
|
@@ -88,14 +90,6 @@ if defined?(Rails)
|
|
88
90
|
end
|
89
91
|
|
90
92
|
module Graphiti
|
91
|
-
autoload :Base, "graphiti/base"
|
92
|
-
|
93
|
-
def self.included(klass)
|
94
|
-
klass.instance_eval do
|
95
|
-
include Base
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
93
|
# @api private
|
100
94
|
def self.context
|
101
95
|
Thread.current[:context] ||= {}
|
@@ -87,6 +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
91
|
}
|
91
92
|
end
|
92
93
|
|
@@ -168,6 +169,8 @@ class Graphiti::Deserializer
|
|
168
169
|
(i[:id] && i[:id] == datum[:id]) ||
|
169
170
|
(i[:'temp-id'] && i[:'temp-id'] == temp_id)
|
170
171
|
}
|
172
|
+
included_idx = included.index(included_object)
|
173
|
+
|
171
174
|
included_object ||= {}
|
172
175
|
included_object[:relationships] ||= {}
|
173
176
|
|
@@ -182,6 +185,7 @@ class Graphiti::Deserializer
|
|
182
185
|
jsonapi_type: datum[:type],
|
183
186
|
temp_id: temp_id,
|
184
187
|
method: method,
|
188
|
+
payload_path: ["included", included_idx],
|
185
189
|
},
|
186
190
|
attributes: attributes,
|
187
191
|
relationships: relationships,
|
data/lib/graphiti/errors.rb
CHANGED
@@ -320,9 +320,7 @@ module Graphiti
|
|
320
320
|
@resource = resource
|
321
321
|
@name = name
|
322
322
|
@flag = flag
|
323
|
-
@exists = opts[:exists] || false
|
324
323
|
@request = opts[:request] || false
|
325
|
-
@guard = opts[:guard]
|
326
324
|
end
|
327
325
|
|
328
326
|
def action
|
@@ -353,20 +351,35 @@ module Graphiti
|
|
353
351
|
end
|
354
352
|
|
355
353
|
def message
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
354
|
+
"#{resource_name}: Tried to #{action} attribute #{@name.inspect}"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class InvalidAttributeAccess < AttributeError
|
359
|
+
def initialize(resource, name, flag, **opts)
|
360
|
+
super
|
361
|
+
@guard = opts[:guard]
|
362
|
+
end
|
363
|
+
|
364
|
+
def message
|
365
|
+
msg = super
|
366
|
+
|
367
|
+
msg << if @guard
|
368
|
+
", but the guard #{@guard.inspect} did not pass."
|
363
369
|
else
|
364
|
-
", but
|
370
|
+
", but the attribute was marked #{@flag.inspect} => false."
|
365
371
|
end
|
372
|
+
|
366
373
|
msg
|
367
374
|
end
|
368
375
|
end
|
369
376
|
|
377
|
+
class UnknownAttribute < AttributeError
|
378
|
+
def message
|
379
|
+
"#{super}, but could not find an attribute with that name."
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
370
383
|
class InvalidJSONArray < Base
|
371
384
|
def initialize(resource, value)
|
372
385
|
@resource = resource
|
@@ -397,7 +410,7 @@ module Graphiti
|
|
397
410
|
|
398
411
|
Or whitelist a secondary endpoint:
|
399
412
|
|
400
|
-
|
413
|
+
secondary_endpoint '/my_url', [:index, :update]
|
401
414
|
|
402
415
|
The current endpoints allowed for this resource are: #{@resource_class.endpoints.inspect}
|
403
416
|
MSG
|
@@ -513,11 +526,14 @@ module Graphiti
|
|
513
526
|
end
|
514
527
|
|
515
528
|
class TypecastFailed < Base
|
516
|
-
|
529
|
+
attr_reader :name, :type_name
|
530
|
+
|
531
|
+
def initialize(resource, name, value, error, type_name)
|
517
532
|
@resource = resource
|
518
533
|
@name = name
|
519
534
|
@value = value
|
520
535
|
@error = error
|
536
|
+
@type_name = type_name
|
521
537
|
end
|
522
538
|
|
523
539
|
def message
|
@@ -735,5 +751,21 @@ module Graphiti
|
|
735
751
|
end
|
736
752
|
end
|
737
753
|
end
|
754
|
+
|
755
|
+
class InvalidRequest < Base
|
756
|
+
attr_reader :errors
|
757
|
+
|
758
|
+
def initialize(errors)
|
759
|
+
@errors = errors
|
760
|
+
end
|
761
|
+
|
762
|
+
def message
|
763
|
+
<<-MSG
|
764
|
+
There were one or more errors with your request:
|
765
|
+
|
766
|
+
#{errors.full_messages.join("\n")}
|
767
|
+
MSG
|
768
|
+
end
|
769
|
+
end
|
738
770
|
end
|
739
771
|
end
|
data/lib/graphiti/railtie.rb
CHANGED
@@ -73,7 +73,7 @@ module Graphiti
|
|
73
73
|
::ActionController::Renderers.add(:jsonapi_errors) do |proxy, options|
|
74
74
|
self.content_type ||= Mime[:jsonapi]
|
75
75
|
|
76
|
-
validation = GraphitiErrors::
|
76
|
+
validation = GraphitiErrors::Validation::Serializer.new \
|
77
77
|
proxy.data, proxy.payload.relationships
|
78
78
|
|
79
79
|
render \
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class RequestValidator
|
3
|
+
attr_reader :errors
|
4
|
+
|
5
|
+
def initialize(root_resource, raw_params)
|
6
|
+
@root_resource = root_resource
|
7
|
+
@raw_params = raw_params
|
8
|
+
@errors = Graphiti::Util::SimpleErrors.new(raw_params)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate
|
12
|
+
resource = @root_resource
|
13
|
+
if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
|
14
|
+
if @root_resource.type != meta_type && @root_resource.polymorphic?
|
15
|
+
resource = @root_resource.class.resource_for_type(meta_type).new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
|
20
|
+
process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
|
21
|
+
|
22
|
+
errors.blank?
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate!
|
26
|
+
unless validate
|
27
|
+
raise Graphiti::Errors::InvalidRequest, self.errors
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def deserialized_payload
|
34
|
+
@deserialized_payload ||= begin
|
35
|
+
payload = normalized_params
|
36
|
+
if payload[:data] && payload[:data][:type]
|
37
|
+
Graphiti::Deserializer.new(payload)
|
38
|
+
else
|
39
|
+
Graphiti::Deserializer.new({})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def process_relationships(resource, relationships, payload_path)
|
47
|
+
opts = {
|
48
|
+
resource: resource,
|
49
|
+
relationships: relationships,
|
50
|
+
}
|
51
|
+
|
52
|
+
Graphiti::Util::RelationshipPayload.iterate(opts) do |x|
|
53
|
+
sideload_def = x[:sideload]
|
54
|
+
|
55
|
+
unless sideload_def.writable?
|
56
|
+
full_key = fully_qualified_key(sideload_def.name, payload_path, :relationships)
|
57
|
+
unless @errors.added?(full_key, :unwritable_relationship)
|
58
|
+
@errors.add(full_key, :unwritable_relationship)
|
59
|
+
end
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
typecast_attributes(x[:resource], x[:attributes], x[:meta][:payload_path])
|
64
|
+
process_relationships(x[:resource], x[:relationships], x[:meta][:payload_path])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def typecast_attributes(resource, attributes, payload_path)
|
69
|
+
attributes.each_pair do |key, value|
|
70
|
+
begin
|
71
|
+
attributes[key] = resource.typecast(key, value, :writable)
|
72
|
+
rescue Graphiti::Errors::UnknownAttribute
|
73
|
+
@errors.add(fully_qualified_key(key, payload_path), :unknown_attribute, message: "is an unknown attribute")
|
74
|
+
rescue Graphiti::Errors::InvalidAttributeAccess
|
75
|
+
@errors.add(fully_qualified_key(key, payload_path), :unwritable_attribute, message: "cannot be written")
|
76
|
+
rescue Graphiti::Errors::TypecastFailed => e
|
77
|
+
@errors.add(fully_qualified_key(key, payload_path), :type_error, message: "should be type #{e.type_name}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalized_params
|
83
|
+
normalized = @raw_params
|
84
|
+
if normalized.respond_to?(:to_unsafe_h)
|
85
|
+
normalized = normalized.to_unsafe_h.deep_symbolize_keys
|
86
|
+
end
|
87
|
+
normalized
|
88
|
+
end
|
89
|
+
|
90
|
+
def fully_qualified_key(key, path, attributes_or_relationships = :attributes)
|
91
|
+
(path + [attributes_or_relationships, key]).join(".")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/graphiti/resource.rb
CHANGED
data/lib/graphiti/runner.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class Runner
|
3
3
|
attr_reader :params
|
4
|
-
|
4
|
+
attr_reader :deserialized_payload
|
5
5
|
|
6
6
|
def initialize(resource_class, params, query = nil)
|
7
7
|
@resource_class = resource_class
|
8
8
|
@params = params
|
9
9
|
@query = query
|
10
|
+
|
11
|
+
validator = RequestValidator.new(jsonapi_resource, params)
|
12
|
+
|
13
|
+
validator.validate!
|
14
|
+
|
15
|
+
@deserialized_payload = validator.deserialized_payload
|
10
16
|
end
|
11
17
|
|
12
18
|
def jsonapi_resource
|
@@ -22,5 +28,49 @@ module Graphiti
|
|
22
28
|
def jsonapi_context
|
23
29
|
Graphiti.context[:object]
|
24
30
|
end
|
31
|
+
|
32
|
+
def query
|
33
|
+
@query ||= Query.new(jsonapi_resource, params)
|
34
|
+
end
|
35
|
+
|
36
|
+
def query_hash
|
37
|
+
@query_hash ||= query.hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def wrap_context
|
41
|
+
Graphiti.with_context(jsonapi_context, action_name.to_sym) do
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def jsonapi_scope(scope, opts = {})
|
47
|
+
jsonapi_resource.build_scope(scope, query, opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def jsonapi_render_options
|
51
|
+
options = {}
|
52
|
+
options.merge!(default_jsonapi_render_options)
|
53
|
+
options[:meta] ||= {}
|
54
|
+
options[:expose] ||= {}
|
55
|
+
options[:expose][:context] = jsonapi_context
|
56
|
+
options
|
57
|
+
end
|
58
|
+
|
59
|
+
def proxy(base = nil, opts = {})
|
60
|
+
base ||= jsonapi_resource.base_scope
|
61
|
+
scope_opts = opts.slice :sideload_parent_length,
|
62
|
+
:default_paginate,
|
63
|
+
:after_resolve,
|
64
|
+
:sideload,
|
65
|
+
:parent,
|
66
|
+
:params
|
67
|
+
scope = jsonapi_scope(base, scope_opts)
|
68
|
+
ResourceProxy.new jsonapi_resource,
|
69
|
+
scope,
|
70
|
+
query,
|
71
|
+
payload: deserialized_payload,
|
72
|
+
single: opts[:single],
|
73
|
+
raise_on_missing: opts[:raise_on_missing]
|
74
|
+
end
|
25
75
|
end
|
26
76
|
end
|
@@ -39,6 +39,10 @@ module Graphiti
|
|
39
39
|
def maybe_raise(opts = {})
|
40
40
|
default = {request: request, exists: true}
|
41
41
|
opts = default.merge(opts)
|
42
|
+
error_class = opts[:exists] ?
|
43
|
+
Graphiti::Errors::InvalidAttributeAccess :
|
44
|
+
Graphiti::Errors::UnknownAttribute
|
45
|
+
|
42
46
|
if raise_error?(opts[:exists])
|
43
47
|
raise error_class.new(resource, name, flag, opts)
|
44
48
|
else
|
@@ -56,10 +60,6 @@ module Graphiti
|
|
56
60
|
attribute[flag] != :required
|
57
61
|
end
|
58
62
|
|
59
|
-
def error_class
|
60
|
-
Errors::AttributeError
|
61
|
-
end
|
62
|
-
|
63
63
|
def supported?
|
64
64
|
attribute[flag] != false
|
65
65
|
end
|
data/lib/graphiti/util/link.rb
CHANGED
@@ -59,7 +59,7 @@ module Graphiti
|
|
59
59
|
|
60
60
|
def params
|
61
61
|
@params ||= {}.tap do |params|
|
62
|
-
if @sideload.type != :belongs_to
|
62
|
+
if @sideload.type != :belongs_to || @sideload.remote?
|
63
63
|
params[:filter] = @sideload.base_filter([@model])
|
64
64
|
end
|
65
65
|
|
@@ -70,7 +70,7 @@ module Graphiti
|
|
70
70
|
def path
|
71
71
|
@path ||=
|
72
72
|
path = @sideload.resource.endpoint[:url].to_s
|
73
|
-
if @sideload.type == :belongs_to
|
73
|
+
if @sideload.type == :belongs_to && !@sideload.remote?
|
74
74
|
path = "#{path}/#{@model.send(@sideload.foreign_key)}"
|
75
75
|
end
|
76
76
|
path
|
@@ -21,8 +21,6 @@ class Graphiti::Util::Persistence
|
|
21
21
|
@resource = @resource.class.resource_for_type(meta_type).new
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
25
|
-
typecast_attributes
|
26
24
|
end
|
27
25
|
|
28
26
|
# Perform the actual save logic.
|
@@ -71,20 +69,6 @@ class Graphiti::Util::Persistence
|
|
71
69
|
|
72
70
|
private
|
73
71
|
|
74
|
-
# In the case where we're sideposting in order to associate 2 nodes
|
75
|
-
# in the graph, the foreign key gets merged into the child's attributes
|
76
|
-
# This attribute should *not* need to be marked writable, as that
|
77
|
-
# would allow writing as a straight attribute instead of just an association
|
78
|
-
def typecast_attributes
|
79
|
-
@attributes.each_pair do |key, value|
|
80
|
-
@attributes[key] = if @foreign_key == key
|
81
|
-
value
|
82
|
-
else
|
83
|
-
@resource.typecast(key, value, :writable)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
72
|
def add_hook(prc, lifecycle_event)
|
89
73
|
::Graphiti::Util::TransactionHooksRecorder.add(prc, lifecycle_event)
|
90
74
|
end
|
@@ -180,12 +164,8 @@ class Graphiti::Util::Persistence
|
|
180
164
|
iterate(except: [:polymorphic_belongs_to, :belongs_to]) do |x|
|
181
165
|
yield x
|
182
166
|
|
183
|
-
|
184
|
-
x[:
|
185
|
-
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships], caller_model, x[:foreign_key])
|
186
|
-
else
|
187
|
-
raise Graphiti::Errors::UnwritableRelationship.new(@resource, x[:sideload])
|
188
|
-
end
|
167
|
+
x[:object] = x[:resource]
|
168
|
+
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships], caller_model, x[:foreign_key])
|
189
169
|
|
190
170
|
processed << x
|
191
171
|
end
|
@@ -195,12 +175,8 @@ class Graphiti::Util::Persistence
|
|
195
175
|
def process_belongs_to(relationships)
|
196
176
|
[].tap do |processed|
|
197
177
|
iterate(only: [:polymorphic_belongs_to, :belongs_to]) do |x|
|
198
|
-
|
199
|
-
x[:
|
200
|
-
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
|
201
|
-
else
|
202
|
-
raise Graphiti::Errors::UnwritableRelationship.new(@resource, x[:sideload])
|
203
|
-
end
|
178
|
+
x[:object] = x[:resource]
|
179
|
+
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
|
204
180
|
processed << x
|
205
181
|
end
|
206
182
|
end
|
@@ -38,7 +38,8 @@ module Graphiti
|
|
38
38
|
private
|
39
39
|
|
40
40
|
def should_yield?(type)
|
41
|
-
(@only.length
|
41
|
+
(@only.length == 0 && @except.length == 0) ||
|
42
|
+
(@only.length > 0 && @only.include?(type)) ||
|
42
43
|
(@except.length > 0 && !@except.include?(type))
|
43
44
|
end
|
44
45
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# A minimal implementation of an errors object similar to `ActiveModel::Errors`.
|
2
|
+
# Designed to support internal Graphiti classes like the `RequestValidator` so
|
3
|
+
# that there does not need to be a dependency on activemodel.
|
4
|
+
module Graphiti
|
5
|
+
module Util
|
6
|
+
class SimpleErrors
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :messages, :details
|
10
|
+
|
11
|
+
def initialize(validation_target)
|
12
|
+
@target = validation_target
|
13
|
+
@messages = apply_default_array({})
|
14
|
+
@details = apply_default_array({})
|
15
|
+
@errors = apply_default_array({})
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear
|
19
|
+
messages.clear
|
20
|
+
details.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](attribute)
|
24
|
+
messages[attribute.to_sym]
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
messages.each_key do |attribute|
|
29
|
+
messages[attribute].each { |error| yield attribute, error }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
values.flatten.size
|
35
|
+
end
|
36
|
+
alias count size
|
37
|
+
|
38
|
+
def values
|
39
|
+
messages.values.reject(&:empty?)
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
messages.select { |key, value|
|
44
|
+
!value.empty?
|
45
|
+
}.keys
|
46
|
+
end
|
47
|
+
|
48
|
+
def empty?
|
49
|
+
size.zero?
|
50
|
+
end
|
51
|
+
alias blank? empty?
|
52
|
+
|
53
|
+
def add(attribute, code, message: nil)
|
54
|
+
message ||= "is #{code.to_s.humanize.downcase}"
|
55
|
+
|
56
|
+
details[attribute.to_sym] << {error: code}
|
57
|
+
messages[attribute.to_sym] << message
|
58
|
+
end
|
59
|
+
|
60
|
+
def added?(attribute, code)
|
61
|
+
details[attribute.to_sym].include?({error: code})
|
62
|
+
end
|
63
|
+
|
64
|
+
def full_messages
|
65
|
+
map { |attribute, message| full_message(attribute, message) }
|
66
|
+
end
|
67
|
+
alias to_a full_messages
|
68
|
+
|
69
|
+
def full_messages_for(attribute)
|
70
|
+
attribute = attribute.to_sym
|
71
|
+
messages[attribute].map { |message| full_message(attribute, message) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def full_message(attribute, message)
|
75
|
+
return message if attribute == :base
|
76
|
+
"#{attribute} #{message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def apply_default_array(hash)
|
82
|
+
hash.default_proc = proc { |h, key| h[key] = [] }
|
83
|
+
hash
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/graphiti/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jsonapi-serializable
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.0
|
47
|
+
version: 1.1.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.0
|
54
|
+
version: 1.1.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: concurrent-ruby
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -246,7 +246,6 @@ files:
|
|
246
246
|
- lib/graphiti/adapters/active_record/many_to_many_sideload.rb
|
247
247
|
- lib/graphiti/adapters/graphiti_api.rb
|
248
248
|
- lib/graphiti/adapters/null.rb
|
249
|
-
- lib/graphiti/base.rb
|
250
249
|
- lib/graphiti/cli.rb
|
251
250
|
- lib/graphiti/configuration.rb
|
252
251
|
- lib/graphiti/context.rb
|
@@ -264,6 +263,7 @@ files:
|
|
264
263
|
- lib/graphiti/rails.rb
|
265
264
|
- lib/graphiti/railtie.rb
|
266
265
|
- lib/graphiti/renderer.rb
|
266
|
+
- lib/graphiti/request_validator.rb
|
267
267
|
- lib/graphiti/resource.rb
|
268
268
|
- lib/graphiti/resource/configuration.rb
|
269
269
|
- lib/graphiti/resource/documentation.rb
|
@@ -311,6 +311,7 @@ files:
|
|
311
311
|
- lib/graphiti/util/serializer_attributes.rb
|
312
312
|
- lib/graphiti/util/serializer_relationships.rb
|
313
313
|
- lib/graphiti/util/sideload.rb
|
314
|
+
- lib/graphiti/util/simple_errors.rb
|
314
315
|
- lib/graphiti/util/transaction_hooks_recorder.rb
|
315
316
|
- lib/graphiti/util/validation_response.rb
|
316
317
|
- lib/graphiti/version.rb
|
@@ -334,7 +335,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
334
335
|
version: '0'
|
335
336
|
requirements: []
|
336
337
|
rubyforge_project:
|
337
|
-
rubygems_version: 2.7.
|
338
|
+
rubygems_version: 2.7.9
|
338
339
|
signing_key:
|
339
340
|
specification_version: 4
|
340
341
|
summary: Easily build jsonapi.org-compatible APIs
|
data/lib/graphiti/base.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
module Graphiti
|
2
|
-
module Base
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
def jsonapi_resource
|
6
|
-
@jsonapi_resource
|
7
|
-
end
|
8
|
-
|
9
|
-
def query
|
10
|
-
@query ||= Query.new(jsonapi_resource, params)
|
11
|
-
end
|
12
|
-
|
13
|
-
def query_hash
|
14
|
-
@query_hash ||= query.hash
|
15
|
-
end
|
16
|
-
|
17
|
-
def wrap_context
|
18
|
-
Graphiti.with_context(jsonapi_context, action_name.to_sym) do
|
19
|
-
yield
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def jsonapi_context
|
24
|
-
self
|
25
|
-
end
|
26
|
-
|
27
|
-
def jsonapi_scope(scope, opts = {})
|
28
|
-
jsonapi_resource.build_scope(scope, query, opts)
|
29
|
-
end
|
30
|
-
|
31
|
-
def normalized_params
|
32
|
-
normalized = params
|
33
|
-
if normalized.respond_to?(:to_unsafe_h)
|
34
|
-
normalized = normalized.to_unsafe_h.deep_symbolize_keys
|
35
|
-
end
|
36
|
-
normalized
|
37
|
-
end
|
38
|
-
|
39
|
-
def deserialized_params
|
40
|
-
@deserialized_params ||= begin
|
41
|
-
payload = normalized_params
|
42
|
-
if payload[:data] && payload[:data][:type]
|
43
|
-
Graphiti::Deserializer.new(payload)
|
44
|
-
else
|
45
|
-
Graphiti::Deserializer.new
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def jsonapi_render_options
|
51
|
-
options = {}
|
52
|
-
options.merge!(default_jsonapi_render_options)
|
53
|
-
options[:meta] ||= {}
|
54
|
-
options[:expose] ||= {}
|
55
|
-
options[:expose][:context] = jsonapi_context
|
56
|
-
options
|
57
|
-
end
|
58
|
-
|
59
|
-
def proxy(base = nil, opts = {})
|
60
|
-
base ||= jsonapi_resource.base_scope
|
61
|
-
scope_opts = opts.slice :sideload_parent_length,
|
62
|
-
:default_paginate,
|
63
|
-
:after_resolve,
|
64
|
-
:sideload,
|
65
|
-
:parent,
|
66
|
-
:params
|
67
|
-
scope = jsonapi_scope(base, scope_opts)
|
68
|
-
ResourceProxy.new jsonapi_resource,
|
69
|
-
scope,
|
70
|
-
query,
|
71
|
-
payload: deserialized_params,
|
72
|
-
single: opts[:single],
|
73
|
-
raise_on_missing: opts[:raise_on_missing]
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|