graphiti 1.0.3 → 1.1.0
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 +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
|