graphiti 1.2.31 → 1.2.36
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/CHANGELOG.md +2 -0
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/lib/graphiti.rb +2 -0
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/persistence/associations.rb +10 -2
- data/lib/graphiti/configuration.rb +3 -1
- data/lib/graphiti/delegates/pagination.rb +1 -0
- data/lib/graphiti/errors.rb +54 -0
- data/lib/graphiti/extensions/extra_attribute.rb +3 -3
- data/lib/graphiti/hash_renderer.rb +161 -22
- data/lib/graphiti/query.rb +12 -1
- data/lib/graphiti/renderer.rb +18 -1
- data/lib/graphiti/request_validators/update_validator.rb +3 -3
- data/lib/graphiti/request_validators/validator.rb +20 -17
- data/lib/graphiti/resource/configuration.rb +21 -1
- data/lib/graphiti/resource/dsl.rb +11 -0
- data/lib/graphiti/resource/interface.rb +1 -1
- data/lib/graphiti/resource/polymorphism.rb +6 -1
- data/lib/graphiti/resource/remote.rb +1 -1
- data/lib/graphiti/resource_proxy.rb +12 -0
- data/lib/graphiti/schema.rb +26 -3
- data/lib/graphiti/schema_diff.rb +38 -0
- data/lib/graphiti/scoping/filter.rb +7 -0
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/serializer.rb +23 -6
- data/lib/graphiti/sideload.rb +11 -1
- data/lib/graphiti/util/class.rb +6 -0
- data/lib/graphiti/util/link.rb +4 -0
- data/lib/graphiti/util/remote_params.rb +9 -4
- data/lib/graphiti/util/remote_serializer.rb +1 -0
- data/lib/graphiti/util/serializer_attributes.rb +21 -6
- data/lib/graphiti/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b7630aef39d3ce4584f53710a5cbe8617addc0a0ea042077c41562accb1c5cf
|
|
4
|
+
data.tar.gz: aad06cd10d1f2604432303990cf92d7b885333d15ccd733a5d0bbab062346872
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 063c97d7d4c719c1cbff7a8028b54a0d862e763bda69a8bae12cbe4a2d5201183b6d516936e9339faf3d35307a1375cf28859253e5fd3c6d1b5824b306fe0d2a
|
|
7
|
+
data.tar.gz: 07dc6893d86bbdfbe676b15707edae40601159d1bb7239b58004280e3c9e5d29c313a9bcc525a2fe7e9582f6191972b8095c0d4a5873a72f15cb5589c9879841
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
3
|
Features:
|
|
4
|
+
- [329](https://github.com/graphiti-api/graphiti/pull/329) Propagate `extra_fields` to related resource links.
|
|
4
5
|
- [242](https://github.com/graphiti-api/graphiti/pull/242) Bump `jsonapi-renderer` to `~0.2.2` now that (https://github.com/jsonapi-rb/jsonapi-renderer/pull/36) is fixed.
|
|
5
6
|
- [158](https://github.com/graphiti-api/graphiti/pull/158) Filters options `allow_nil: true`
|
|
6
7
|
Option can be set at the resource level `Resource.filters_accept_nil_by_default = true`.
|
|
@@ -10,6 +11,7 @@ Features:
|
|
|
10
11
|
|
|
11
12
|
Fixes:
|
|
12
13
|
- [282] Support model names including "Resource"
|
|
14
|
+
- [313](https://github.com/graphiti-api/graphiti/pull/313) Sort remote resources in schema generation
|
|
13
15
|
|
|
14
16
|
## 1.1.0
|
|
15
17
|
|
data/lib/graphiti.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "active_support/core_ext/class/attribute"
|
|
|
6
6
|
require "active_support/core_ext/hash/conversions" # to_xml
|
|
7
7
|
require "active_support/concern"
|
|
8
8
|
require "active_support/time"
|
|
9
|
+
require "active_support/deprecation"
|
|
9
10
|
|
|
10
11
|
require "dry-types"
|
|
11
12
|
require "graphiti_errors"
|
|
@@ -141,6 +142,7 @@ require "graphiti/scoping/sort"
|
|
|
141
142
|
require "graphiti/scoping/paginate"
|
|
142
143
|
require "graphiti/scoping/extra_attributes"
|
|
143
144
|
require "graphiti/scoping/filterable"
|
|
145
|
+
require "graphiti/scoping/filter_group_validator"
|
|
144
146
|
require "graphiti/scoping/default_filter"
|
|
145
147
|
require "graphiti/scoping/filter"
|
|
146
148
|
require "graphiti/stats/dsl"
|
|
@@ -34,7 +34,7 @@ module Graphiti
|
|
|
34
34
|
|
|
35
35
|
def build_url(scope)
|
|
36
36
|
url = resource.remote_url
|
|
37
|
-
params = scope[:params].merge(scope.except(:params))
|
|
37
|
+
params = scope[:params].merge(scope.except(:params, :foreign_key))
|
|
38
38
|
params[:page] ||= {}
|
|
39
39
|
params[:page][:size] ||= 999
|
|
40
40
|
params = CGI.unescape(params.to_query)
|
|
@@ -11,8 +11,16 @@ module Graphiti
|
|
|
11
11
|
.persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
|
|
12
12
|
processed << x
|
|
13
13
|
rescue Graphiti::Errors::RecordNotFound
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if Graphiti.config.raise_on_missing_sidepost
|
|
15
|
+
path = "relationships/#{x.dig(:meta, :jsonapi_type)}"
|
|
16
|
+
raise Graphiti::Errors::RecordNotFound.new(x[:sideload].name, id, path)
|
|
17
|
+
else
|
|
18
|
+
pointer = "data/relationships/#{x.dig(:meta, :jsonapi_type)}"
|
|
19
|
+
object = Graphiti::Errors::NullRelation.new(id.to_s, pointer)
|
|
20
|
+
object.errors.add(:base, :not_found, message: "could not be found")
|
|
21
|
+
x[:object] = object
|
|
22
|
+
processed << x
|
|
23
|
+
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
18
26
|
end
|
|
@@ -14,6 +14,7 @@ module Graphiti
|
|
|
14
14
|
attr_accessor :pagination_links_on_demand
|
|
15
15
|
attr_accessor :pagination_links
|
|
16
16
|
attr_accessor :typecast_reads
|
|
17
|
+
attr_accessor :raise_on_missing_sidepost
|
|
17
18
|
|
|
18
19
|
attr_reader :debug, :debug_models
|
|
19
20
|
|
|
@@ -29,6 +30,7 @@ module Graphiti
|
|
|
29
30
|
@pagination_links_on_demand = false
|
|
30
31
|
@pagination_links = false
|
|
31
32
|
@typecast_reads = true
|
|
33
|
+
@raise_on_missing_sidepost = true
|
|
32
34
|
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
|
|
33
35
|
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
|
|
34
36
|
|
|
@@ -43,7 +45,7 @@ module Graphiti
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
if (logger = ::Rails.logger)
|
|
46
|
-
self.debug = logger.
|
|
48
|
+
self.debug = logger.debug? && debug
|
|
47
49
|
Graphiti.logger = logger
|
|
48
50
|
end
|
|
49
51
|
end
|
|
@@ -11,6 +11,7 @@ module Graphiti
|
|
|
11
11
|
|
|
12
12
|
def links
|
|
13
13
|
@links ||= {}.tap do |links|
|
|
14
|
+
links[:self] = pagination_link(current_page)
|
|
14
15
|
links[:first] = pagination_link(1)
|
|
15
16
|
links[:last] = pagination_link(last_page)
|
|
16
17
|
links[:prev] = pagination_link(current_page - 1) unless current_page == 1
|
data/lib/graphiti/errors.rb
CHANGED
|
@@ -2,6 +2,31 @@ module Graphiti
|
|
|
2
2
|
module Errors
|
|
3
3
|
class Base < StandardError; end
|
|
4
4
|
|
|
5
|
+
class UnreadableAttribute < Base
|
|
6
|
+
def initialize(resource_class, name)
|
|
7
|
+
@resource_class = resource_class
|
|
8
|
+
@name = name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def message
|
|
12
|
+
"#{@resource_class}: Requested field #{@name}, but not authorized to read it"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class NullRelation
|
|
17
|
+
attr_accessor :id, :errors, :pointer
|
|
18
|
+
|
|
19
|
+
def initialize(id, pointer)
|
|
20
|
+
@id = id
|
|
21
|
+
@pointer = pointer
|
|
22
|
+
@errors = Graphiti::Util::SimpleErrors.new(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.human_attribute_name(attr, options = {})
|
|
26
|
+
attr
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
5
30
|
class AdapterNotImplemented < Base
|
|
6
31
|
def initialize(adapter, attribute, method)
|
|
7
32
|
@adapter = adapter
|
|
@@ -791,5 +816,34 @@ module Graphiti
|
|
|
791
816
|
|
|
792
817
|
class ConflictRequest < InvalidRequest
|
|
793
818
|
end
|
|
819
|
+
|
|
820
|
+
class FilterGroupInvalidRequirement < Base
|
|
821
|
+
def initialize(resource, valid_required_values)
|
|
822
|
+
@resource = resource
|
|
823
|
+
@valid_required_values = valid_required_values
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def message
|
|
827
|
+
<<-MSG.gsub(/\s+/, " ").strip
|
|
828
|
+
The filter group required: value on resource #{@resource.class} must be one of the following:
|
|
829
|
+
#{@valid_required_values.join(", ")}
|
|
830
|
+
MSG
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
class FilterGroupMissingRequiredFilters < Base
|
|
835
|
+
def initialize(resource, filter_names, required)
|
|
836
|
+
@resource = resource
|
|
837
|
+
@filter_names = filter_names
|
|
838
|
+
@required_label = required == :all ? "All" : "One"
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
def message
|
|
842
|
+
<<-MSG.gsub(/\s+/, " ").strip
|
|
843
|
+
#{@required_label} of the following filters must be provided on resource #{@resource.type}:
|
|
844
|
+
#{@filter_names.join(", ")}
|
|
845
|
+
MSG
|
|
846
|
+
end
|
|
847
|
+
end
|
|
794
848
|
end
|
|
795
849
|
end
|
|
@@ -46,9 +46,9 @@ module Graphiti
|
|
|
46
46
|
next false unless instance_exec(&options[:if])
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
@extra_fields
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
next false unless @extra_fields
|
|
50
|
+
|
|
51
|
+
@extra_fields[@_type]&.include?(name) || @extra_fields[@resource&.type]&.include?(name)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
attribute name, if: allow_field, &blk
|
|
@@ -1,54 +1,193 @@
|
|
|
1
1
|
module Graphiti
|
|
2
2
|
module SerializableHash
|
|
3
|
-
def to_hash(fields: nil, include: {})
|
|
3
|
+
def to_hash(fields: nil, include: {}, name_chain: [], graphql: false)
|
|
4
4
|
{}.tap do |hash|
|
|
5
|
-
|
|
5
|
+
if fields
|
|
6
|
+
fields_list = nil
|
|
7
|
+
|
|
8
|
+
# Dot syntax wins over jsonapi type
|
|
9
|
+
if name_chain.length > 0
|
|
10
|
+
fields_list = fields[name_chain.join(".").to_sym]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if fields_list.nil?
|
|
14
|
+
fields_list = fields[jsonapi_type]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# polymorphic resources - merge the PARENT type
|
|
19
|
+
if polymorphic_subclass?
|
|
20
|
+
if fields[@resource.type]
|
|
21
|
+
fields_list ||= []
|
|
22
|
+
fields_list |= fields[@resource.type]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if fields[jsonapi_type]
|
|
26
|
+
fields_list ||= []
|
|
27
|
+
fields_list |= fields[jsonapi_type]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
6
31
|
attrs = requested_attributes(fields_list).each_with_object({}) { |(k, v), h|
|
|
7
|
-
|
|
32
|
+
name = graphql ? k.to_s.camelize(:lower).to_sym : k
|
|
33
|
+
h[name] = instance_eval(&v)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# The main logic here is just !!include[k]
|
|
37
|
+
# But we also have special on__<type>--<name> includes
|
|
38
|
+
# Where we only include when matching the polymorphic type
|
|
39
|
+
rels = @_relationships.select { |k, v|
|
|
40
|
+
if include[k]
|
|
41
|
+
true
|
|
42
|
+
else
|
|
43
|
+
included = false
|
|
44
|
+
include.keys.each do |key|
|
|
45
|
+
split = key.to_s.split(/^on__/)
|
|
46
|
+
if split.length > 1
|
|
47
|
+
requested_type, key = split[1].split("--")
|
|
48
|
+
if requested_type.to_sym == jsonapi_type
|
|
49
|
+
included = k == key.to_sym
|
|
50
|
+
break
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
included
|
|
55
|
+
end
|
|
8
56
|
}
|
|
9
|
-
|
|
57
|
+
|
|
10
58
|
rels.each_with_object({}) do |(k, v), h|
|
|
59
|
+
nested_include = include[k]
|
|
60
|
+
|
|
61
|
+
# This logic only fires if it's a special on__<type>--<name> include
|
|
62
|
+
unless include.has_key?(k)
|
|
63
|
+
include.keys.each do |include_key|
|
|
64
|
+
if k == include_key.to_s.split("--")[1].to_sym
|
|
65
|
+
nested_include = include[include_key]
|
|
66
|
+
break
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
11
71
|
serializers = v.send(:resources)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
72
|
+
name = graphql ? k.to_s.camelize(:lower) : k
|
|
73
|
+
name_chain = name_chain.dup
|
|
74
|
+
name_chain << k unless name_chain.last == k
|
|
75
|
+
|
|
76
|
+
unless remote_resource? && serializers.nil?
|
|
77
|
+
payload = if serializers.is_a?(Array)
|
|
78
|
+
data = serializers.map { |rr|
|
|
79
|
+
rr.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
|
|
80
|
+
}
|
|
81
|
+
graphql ? {nodes: data} : data
|
|
82
|
+
elsif serializers.nil?
|
|
83
|
+
if @resource.class.respond_to?(:sideload)
|
|
84
|
+
if @resource.class.sideload(k).type.to_s.include?("_many")
|
|
85
|
+
graphql ? {nodes: []} : []
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
serializers.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
|
|
15
90
|
end
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
91
|
+
|
|
92
|
+
attrs[name.to_sym] = payload
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if !graphql || (fields_list || []).include?(:id)
|
|
97
|
+
hash[:id] = jsonapi_id
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if (fields_list || []).include?(:_type)
|
|
101
|
+
hash[:_type] = jsonapi_type.to_s
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if (fields_list || []).include?(:__typename)
|
|
105
|
+
resource_class = @resource.class
|
|
106
|
+
if polymorphic_subclass?
|
|
107
|
+
resource_class = @resource.class.resource_for_type(jsonapi_type)
|
|
20
108
|
end
|
|
109
|
+
hash[:__typename] = ::Graphiti::Util::Class
|
|
110
|
+
.graphql_type_name(resource_class.name)
|
|
21
111
|
end
|
|
22
112
|
|
|
23
|
-
hash[:id] = jsonapi_id
|
|
24
113
|
hash.merge!(attrs) if attrs.any?
|
|
25
114
|
end
|
|
26
115
|
end
|
|
116
|
+
|
|
117
|
+
def polymorphic_subclass?
|
|
118
|
+
!remote_resource? &&
|
|
119
|
+
@resource.polymorphic? &&
|
|
120
|
+
@resource.type != jsonapi_type
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# See hack in util/remote_serializer.rb
|
|
124
|
+
def remote_resource?
|
|
125
|
+
@resource == 1
|
|
126
|
+
end
|
|
27
127
|
end
|
|
28
128
|
|
|
29
129
|
class HashRenderer
|
|
30
|
-
def initialize(resource)
|
|
130
|
+
def initialize(resource, graphql: false)
|
|
31
131
|
@resource = resource
|
|
132
|
+
@graphql = graphql
|
|
32
133
|
end
|
|
33
134
|
|
|
34
135
|
def render(options)
|
|
35
136
|
serializers = options[:data]
|
|
36
137
|
opts = options.slice(:fields, :include)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
138
|
+
opts[:graphql] = @graphql
|
|
139
|
+
top_level_key = get_top_level_key(@resource, serializers.is_a?(Array))
|
|
140
|
+
|
|
141
|
+
hash = {top_level_key => {}}
|
|
142
|
+
nodes = get_nodes(serializers, opts)
|
|
143
|
+
add_nodes(hash, top_level_key, options, nodes, @graphql)
|
|
144
|
+
add_stats(hash, top_level_key, options, @graphql)
|
|
145
|
+
hash
|
|
40
146
|
end
|
|
41
147
|
|
|
42
148
|
private
|
|
43
149
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
150
|
+
def get_top_level_key(resource, is_many)
|
|
151
|
+
key = :data
|
|
152
|
+
|
|
153
|
+
if @graphql
|
|
154
|
+
key = @resource.graphql_entrypoint
|
|
155
|
+
key = key.to_s.singularize.to_sym unless is_many
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
key
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def get_nodes(serializers, opts)
|
|
162
|
+
if serializers.is_a?(Array)
|
|
163
|
+
serializers.map do |s|
|
|
164
|
+
s.to_hash(**opts)
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
serializers.to_hash(**opts)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def add_nodes(hash, top_level_key, opts, nodes, graphql)
|
|
172
|
+
payload = nodes
|
|
173
|
+
if graphql && nodes.is_a?(Array)
|
|
174
|
+
payload = {nodes: nodes}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Don't render nodes if we only requested stats
|
|
178
|
+
unless graphql && opts[:fields].values == [[:stats]]
|
|
179
|
+
hash[top_level_key] = payload
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def add_stats(hash, top_level_key, options, graphql)
|
|
184
|
+
if options[:meta] && !options[:meta].empty?
|
|
185
|
+
if @graphql
|
|
186
|
+
if (stats = options[:meta][:stats])
|
|
187
|
+
hash[top_level_key][:stats] = stats
|
|
49
188
|
end
|
|
50
189
|
else
|
|
51
|
-
|
|
190
|
+
hash.merge!(options.slice(:meta))
|
|
52
191
|
end
|
|
53
192
|
end
|
|
54
193
|
end
|
data/lib/graphiti/query.rb
CHANGED
|
@@ -96,7 +96,18 @@ module Graphiti
|
|
|
96
96
|
sl_resource = resource_for_sideload(sideload)
|
|
97
97
|
query_parents = parents + [self]
|
|
98
98
|
sub_hash = sub_hash[:include] if sub_hash.key?(:include)
|
|
99
|
-
|
|
99
|
+
|
|
100
|
+
# NB: To handle on__<type>--<name>
|
|
101
|
+
# A) relationship_name == :positions
|
|
102
|
+
# B) key == on__employees.positions
|
|
103
|
+
# This way A) ensures sideloads are resolved
|
|
104
|
+
# And B) ensures nested filters, sorts etc still work
|
|
105
|
+
relationship_name = sideload ? sideload.name : key
|
|
106
|
+
hash[relationship_name] = Query.new sl_resource,
|
|
107
|
+
@params,
|
|
108
|
+
key,
|
|
109
|
+
sub_hash,
|
|
110
|
+
query_parents, :all
|
|
100
111
|
else
|
|
101
112
|
handle_missing_sideload(key)
|
|
102
113
|
end
|
data/lib/graphiti/renderer.rb
CHANGED
|
@@ -17,8 +17,20 @@ module Graphiti
|
|
|
17
17
|
render(self.class.jsonapi_renderer).to_json
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def as_graphql
|
|
21
|
+
render(self.class.graphql_renderer(@proxy))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_graphql
|
|
25
|
+
as_graphql.to_json
|
|
26
|
+
end
|
|
27
|
+
|
|
20
28
|
def to_json
|
|
21
|
-
|
|
29
|
+
as_json.to_json
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def as_json
|
|
33
|
+
render(self.class.hash_renderer(@proxy))
|
|
22
34
|
end
|
|
23
35
|
|
|
24
36
|
def to_xml
|
|
@@ -35,6 +47,11 @@ module Graphiti
|
|
|
35
47
|
JSONAPI::Serializable::Renderer.new(implementation)
|
|
36
48
|
end
|
|
37
49
|
|
|
50
|
+
def self.graphql_renderer(proxy)
|
|
51
|
+
implementation = Graphiti::HashRenderer.new(proxy.resource, graphql: true)
|
|
52
|
+
JSONAPI::Serializable::Renderer.new(implementation)
|
|
53
|
+
end
|
|
54
|
+
|
|
38
55
|
private
|
|
39
56
|
|
|
40
57
|
def render(renderer)
|
|
@@ -26,17 +26,17 @@ module Graphiti
|
|
|
26
26
|
[:data, :type],
|
|
27
27
|
[:data, :id]
|
|
28
28
|
].each do |required_attr|
|
|
29
|
-
attribute_mismatch(required_attr) unless @
|
|
29
|
+
attribute_mismatch(required_attr) unless @params.dig(*required_attr)
|
|
30
30
|
end
|
|
31
31
|
errors.blank?
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def payload_matches_endpoint?
|
|
35
|
-
unless @
|
|
35
|
+
unless @params.dig(:data, :id) == @params.dig(:filter, :id)
|
|
36
36
|
attribute_mismatch([:data, :id])
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
meta_type = @
|
|
39
|
+
meta_type = @params.dig(:data, :type)
|
|
40
40
|
|
|
41
41
|
# NOTE: calling #to_s and comparing 2 strings is slower than
|
|
42
42
|
# calling #to_sym and comparing 2 symbols. But pre ruby-2.2
|
|
@@ -5,21 +5,31 @@ module Graphiti
|
|
|
5
5
|
|
|
6
6
|
def initialize(root_resource, raw_params, action)
|
|
7
7
|
@root_resource = root_resource
|
|
8
|
-
@
|
|
8
|
+
@params = normalized_params(raw_params)
|
|
9
9
|
@errors = Graphiti::Util::SimpleErrors.new(raw_params)
|
|
10
10
|
@action = action
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def validate
|
|
14
|
+
# Right now, all requests - even reads - go through the validator
|
|
15
|
+
# In the future these should have their own validation logic, but
|
|
16
|
+
# for now we can just bypass
|
|
17
|
+
return true unless @params.has_key?(:data)
|
|
18
|
+
|
|
14
19
|
resource = @root_resource
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
if @params[:data].has_key?(:type)
|
|
22
|
+
if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
|
|
23
|
+
if @root_resource.type != meta_type && @root_resource.polymorphic?
|
|
24
|
+
resource = @root_resource.class.resource_for_type(meta_type).new
|
|
25
|
+
end
|
|
18
26
|
end
|
|
19
|
-
end
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
|
|
29
|
+
process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
|
|
30
|
+
else
|
|
31
|
+
errors.add(:"data.type", :missing)
|
|
32
|
+
end
|
|
23
33
|
|
|
24
34
|
errors.blank?
|
|
25
35
|
end
|
|
@@ -33,14 +43,7 @@ module Graphiti
|
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def deserialized_payload
|
|
36
|
-
@deserialized_payload ||=
|
|
37
|
-
payload = normalized_params
|
|
38
|
-
if payload[:data] && payload[:data][:type]
|
|
39
|
-
Graphiti::Deserializer.new(payload)
|
|
40
|
-
else
|
|
41
|
-
Graphiti::Deserializer.new({})
|
|
42
|
-
end
|
|
43
|
-
end
|
|
46
|
+
@deserialized_payload ||= Graphiti::Deserializer.new(@params)
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
private
|
|
@@ -86,8 +89,8 @@ module Graphiti
|
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
89
|
-
def normalized_params
|
|
90
|
-
normalized =
|
|
92
|
+
def normalized_params(raw_params)
|
|
93
|
+
normalized = raw_params
|
|
91
94
|
if normalized.respond_to?(:to_unsafe_h)
|
|
92
95
|
normalized = normalized.to_unsafe_h.deep_symbolize_keys
|
|
93
96
|
end
|
|
@@ -28,6 +28,14 @@ module Graphiti
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def graphql_entrypoint=(val)
|
|
32
|
+
if val
|
|
33
|
+
super(val.to_s.camelize(:lower).to_sym)
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
# The .stat call stores a proc based on adapter
|
|
32
40
|
# So if we assign a new adapter, reconfigure
|
|
33
41
|
def adapter=(val)
|
|
@@ -83,7 +91,8 @@ module Graphiti
|
|
|
83
91
|
:relationships_readable_by_default,
|
|
84
92
|
:relationships_writable_by_default,
|
|
85
93
|
:filters_accept_nil_by_default,
|
|
86
|
-
:filters_deny_empty_by_default
|
|
94
|
+
:filters_deny_empty_by_default,
|
|
95
|
+
:graphql_entrypoint
|
|
87
96
|
|
|
88
97
|
class << self
|
|
89
98
|
prepend Overrides
|
|
@@ -97,6 +106,7 @@ module Graphiti
|
|
|
97
106
|
# re-assigning causes a new Class.new
|
|
98
107
|
klass.serializer = (klass.serializer || klass.infer_serializer_superclass)
|
|
99
108
|
klass.type ||= klass.infer_type
|
|
109
|
+
klass.graphql_entrypoint = klass.type.to_s.pluralize.to_sym
|
|
100
110
|
default(klass, :attributes_readable_by_default, true)
|
|
101
111
|
default(klass, :attributes_writable_by_default, true)
|
|
102
112
|
default(klass, :attributes_sortable_by_default, true)
|
|
@@ -144,6 +154,7 @@ module Graphiti
|
|
|
144
154
|
if (@abstract_class = val)
|
|
145
155
|
self.serializer = nil
|
|
146
156
|
self.type = nil
|
|
157
|
+
self.graphql_entrypoint = nil
|
|
147
158
|
end
|
|
148
159
|
end
|
|
149
160
|
|
|
@@ -188,6 +199,7 @@ module Graphiti
|
|
|
188
199
|
@config ||=
|
|
189
200
|
{
|
|
190
201
|
filters: {},
|
|
202
|
+
grouped_filters: {},
|
|
191
203
|
default_filters: {},
|
|
192
204
|
stats: {},
|
|
193
205
|
sort_all: nil,
|
|
@@ -224,6 +236,10 @@ module Graphiti
|
|
|
224
236
|
config[:filters]
|
|
225
237
|
end
|
|
226
238
|
|
|
239
|
+
def grouped_filters
|
|
240
|
+
config[:grouped_filters]
|
|
241
|
+
end
|
|
242
|
+
|
|
227
243
|
def sorts
|
|
228
244
|
config[:sorts]
|
|
229
245
|
end
|
|
@@ -262,6 +278,10 @@ module Graphiti
|
|
|
262
278
|
self.class.filters
|
|
263
279
|
end
|
|
264
280
|
|
|
281
|
+
def grouped_filters
|
|
282
|
+
self.class.grouped_filters
|
|
283
|
+
end
|
|
284
|
+
|
|
265
285
|
def sort_all
|
|
266
286
|
self.class.sort_all
|
|
267
287
|
end
|
|
@@ -44,6 +44,17 @@ module Graphiti
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
def filter_group(filter_names, *args)
|
|
48
|
+
opts = args.extract_options!
|
|
49
|
+
|
|
50
|
+
Scoping::FilterGroupValidator.raise_unless_filter_group_requirement_valid!(self, opts[:required])
|
|
51
|
+
|
|
52
|
+
config[:grouped_filters] = {
|
|
53
|
+
names: filter_names,
|
|
54
|
+
required: opts[:required]
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
47
58
|
def sort_all(&blk)
|
|
48
59
|
if block_given?
|
|
49
60
|
config[:_sort_all] = blk
|
|
@@ -42,9 +42,14 @@ module Graphiti
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def sideload(name)
|
|
45
|
-
|
|
45
|
+
if (split_on = name.to_s.split(/^on__/)).length > 1
|
|
46
|
+
on_type, name = split_on[1].split("--").map(&:to_sym)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sl = super(name)
|
|
46
50
|
if !polymorphic_child? && sl.nil?
|
|
47
51
|
children.each do |c|
|
|
52
|
+
next if on_type && c.type != on_type
|
|
48
53
|
break if (sl = c.sideloads[name])
|
|
49
54
|
end
|
|
50
55
|
end
|
|
@@ -47,10 +47,22 @@ module Graphiti
|
|
|
47
47
|
Renderer.new(self, options).to_json
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def as_json(options = {})
|
|
51
|
+
Renderer.new(self, options).as_json
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
def to_xml(options = {})
|
|
51
55
|
Renderer.new(self, options).to_xml
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
def to_graphql(options = {})
|
|
59
|
+
Renderer.new(self, options).to_graphql
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def as_graphql(options = {})
|
|
63
|
+
Renderer.new(self, options).as_graphql
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
def data
|
|
55
67
|
@data ||= begin
|
|
56
68
|
records = @scope.resolve
|
data/lib/graphiti/schema.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Graphiti
|
|
|
7
7
|
::Rails.application.eager_load! if defined?(::Rails)
|
|
8
8
|
resources ||= Graphiti.resources.reject(&:abstract_class?)
|
|
9
9
|
resources.reject! { |r| r.name.nil? }
|
|
10
|
+
|
|
10
11
|
new(resources).generate
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -25,7 +26,7 @@ module Graphiti
|
|
|
25
26
|
|
|
26
27
|
def initialize(resources)
|
|
27
28
|
@resources = resources.sort_by(&:name)
|
|
28
|
-
@remote_resources = resources.select(&:remote?)
|
|
29
|
+
@remote_resources = @resources.select(&:remote?)
|
|
29
30
|
@local_resources = @resources - @remote_resources
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -89,14 +90,20 @@ module Graphiti
|
|
|
89
90
|
config = {
|
|
90
91
|
name: r.name,
|
|
91
92
|
type: r.type.to_s,
|
|
93
|
+
graphql_entrypoint: r.graphql_entrypoint.to_s,
|
|
92
94
|
description: r.description,
|
|
93
95
|
attributes: attributes(r),
|
|
94
96
|
extra_attributes: extra_attributes(r),
|
|
95
97
|
sorts: sorts(r),
|
|
96
98
|
filters: filters(r),
|
|
97
|
-
relationships: relationships(r)
|
|
99
|
+
relationships: relationships(r),
|
|
100
|
+
stats: stats(r)
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
if r.grouped_filters.any?
|
|
104
|
+
config[:filter_group] = r.grouped_filters
|
|
105
|
+
end
|
|
106
|
+
|
|
100
107
|
if r.default_sort
|
|
101
108
|
default_sort = r.default_sort.map { |s|
|
|
102
109
|
{s.keys.first.to_s => s.values.first.to_s}
|
|
@@ -108,7 +115,7 @@ module Graphiti
|
|
|
108
115
|
config[:default_page_size] = r.default_page_size
|
|
109
116
|
end
|
|
110
117
|
|
|
111
|
-
if r.polymorphic?
|
|
118
|
+
if r.polymorphic? && !r.polymorphic_child?
|
|
112
119
|
config[:polymorphic] = true
|
|
113
120
|
config[:children] = r.children.map(&:name)
|
|
114
121
|
end
|
|
@@ -163,6 +170,14 @@ module Graphiti
|
|
|
163
170
|
end
|
|
164
171
|
end
|
|
165
172
|
|
|
173
|
+
def stats(resource)
|
|
174
|
+
{}.tap do |stats|
|
|
175
|
+
resource.stats.each_pair do |name, config|
|
|
176
|
+
stats[name] = config.calculations.keys
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
166
181
|
def sorts(resource)
|
|
167
182
|
{}.tap do |s|
|
|
168
183
|
resource.sorts.each_pair do |name, sort|
|
|
@@ -202,11 +217,18 @@ module Graphiti
|
|
|
202
217
|
config[:guard] = true
|
|
203
218
|
end
|
|
204
219
|
end
|
|
220
|
+
if filter[:required] # one-off filter, not attribute
|
|
221
|
+
config[:required] = true
|
|
222
|
+
end
|
|
205
223
|
f[name] = config
|
|
206
224
|
end
|
|
207
225
|
end
|
|
208
226
|
end
|
|
209
227
|
|
|
228
|
+
def filter_group(resource)
|
|
229
|
+
resource.config[:grouped_filters]
|
|
230
|
+
end
|
|
231
|
+
|
|
210
232
|
def relationships(resource)
|
|
211
233
|
{}.tap do |r|
|
|
212
234
|
resource.sideloads.each_pair do |name, config|
|
|
@@ -214,6 +236,7 @@ module Graphiti
|
|
|
214
236
|
if config.type == :polymorphic_belongs_to
|
|
215
237
|
schema[:resources] = config.children.values
|
|
216
238
|
.map(&:resource).map(&:class).map(&:name)
|
|
239
|
+
schema[:parent_resource] = config.parent_resource.class.name
|
|
217
240
|
else
|
|
218
241
|
schema[:resource] = config.resource.class.name
|
|
219
242
|
end
|
data/lib/graphiti/schema_diff.rb
CHANGED
|
@@ -30,6 +30,8 @@ module Graphiti
|
|
|
30
30
|
compare_extra_attributes(r, new_resource)
|
|
31
31
|
compare_sorts(r, new_resource)
|
|
32
32
|
compare_filters(r, new_resource)
|
|
33
|
+
compare_filter_group(r, new_resource)
|
|
34
|
+
compare_stats(r, new_resource)
|
|
33
35
|
compare_relationships(r, new_resource)
|
|
34
36
|
end
|
|
35
37
|
end
|
|
@@ -204,6 +206,42 @@ module Graphiti
|
|
|
204
206
|
end
|
|
205
207
|
end
|
|
206
208
|
|
|
209
|
+
def compare_filter_group(old_resource, new_resource)
|
|
210
|
+
if new_resource[:filter_group]
|
|
211
|
+
if old_resource[:filter_group]
|
|
212
|
+
new_names = new_resource[:filter_group][:names]
|
|
213
|
+
old_names = old_resource[:filter_group][:names]
|
|
214
|
+
diff = new_names - old_names
|
|
215
|
+
if !diff.empty? && new_resource[:filter_group][:required] == :all
|
|
216
|
+
@errors << "#{old_resource[:name]}: all required filter group #{old_names.inspect} added #{"member".pluralize(diff.length)} #{diff.inspect}."
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
old_required = old_resource[:filter_group][:required]
|
|
220
|
+
new_required = new_resource[:filter_group][:required]
|
|
221
|
+
if old_required == :any && new_required == :all
|
|
222
|
+
@errors << "#{old_resource[:name]}: filter group #{old_names.inspect} moved from required: :any to required: :all"
|
|
223
|
+
end
|
|
224
|
+
else
|
|
225
|
+
@errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].inspect} was added."
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def compare_stats(old_resource, new_resource)
|
|
231
|
+
old_resource[:stats].each_pair do |name, old_calculations|
|
|
232
|
+
new_calculations = new_resource[:stats][name]
|
|
233
|
+
if new_calculations
|
|
234
|
+
old_calculations.each do |calc|
|
|
235
|
+
unless new_calculations.include?(calc)
|
|
236
|
+
@errors << "#{old_resource[:name]}: calculation #{calc.inspect} was removed from stat #{name.inspect}."
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
@errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
207
245
|
def compare_endpoints
|
|
208
246
|
@old[:endpoints].each_pair do |path, old_endpoint|
|
|
209
247
|
unless (new_endpoint = @new[:endpoints][path])
|
|
@@ -3,6 +3,13 @@ module Graphiti
|
|
|
3
3
|
include Scoping::Filterable
|
|
4
4
|
|
|
5
5
|
def apply
|
|
6
|
+
unless @opts[:bypass_required_filters]
|
|
7
|
+
Graphiti::Scoping::FilterGroupValidator.new(
|
|
8
|
+
resource,
|
|
9
|
+
query_hash
|
|
10
|
+
).raise_unless_filter_group_requirements_met!
|
|
11
|
+
end
|
|
12
|
+
|
|
6
13
|
if missing_required_filters.any? && !@opts[:bypass_required_filters]
|
|
7
14
|
raise Errors::RequiredFilter.new(resource, missing_required_filters)
|
|
8
15
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Graphiti
|
|
2
|
+
class Scoping::FilterGroupValidator
|
|
3
|
+
VALID_REQUIRED_VALUES = %i[all any]
|
|
4
|
+
|
|
5
|
+
def self.raise_unless_filter_group_requirement_valid!(resource, requirement)
|
|
6
|
+
unless VALID_REQUIRED_VALUES.include?(requirement)
|
|
7
|
+
raise Errors::FilterGroupInvalidRequirement.new(
|
|
8
|
+
resource,
|
|
9
|
+
VALID_REQUIRED_VALUES
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(resource, query_hash)
|
|
17
|
+
@resource = resource
|
|
18
|
+
@query_hash = query_hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def raise_unless_filter_group_requirements_met!
|
|
22
|
+
return if grouped_filters.empty?
|
|
23
|
+
|
|
24
|
+
case filter_group_requirement
|
|
25
|
+
when :all
|
|
26
|
+
raise_unless_all_requirements_met!
|
|
27
|
+
when :any
|
|
28
|
+
raise_unless_any_requirements_met!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :resource, :query_hash
|
|
37
|
+
|
|
38
|
+
def raise_unless_all_requirements_met!
|
|
39
|
+
met = filter_group_names.all? { |filter_name| filter_group_filter_param.key?(filter_name) }
|
|
40
|
+
|
|
41
|
+
unless met
|
|
42
|
+
raise Errors::FilterGroupMissingRequiredFilters.new(
|
|
43
|
+
resource,
|
|
44
|
+
filter_group_names,
|
|
45
|
+
filter_group_requirement
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def raise_unless_any_requirements_met!
|
|
51
|
+
met = filter_group_names.any? { |filter_name| filter_group_filter_param.key?(filter_name) }
|
|
52
|
+
|
|
53
|
+
unless met
|
|
54
|
+
raise Errors::FilterGroupMissingRequiredFilters.new(
|
|
55
|
+
resource,
|
|
56
|
+
filter_group_names,
|
|
57
|
+
filter_group_requirement
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def filter_group_names
|
|
63
|
+
grouped_filters.fetch(:names, [])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def filter_group_requirement
|
|
67
|
+
grouped_filters.fetch(:required, :invalid)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def grouped_filters
|
|
71
|
+
resource.grouped_filters
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def filter_group_filter_param
|
|
75
|
+
query_hash.fetch(:filter, {})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/graphiti/serializer.rb
CHANGED
|
@@ -12,13 +12,36 @@ module Graphiti
|
|
|
12
12
|
# go through type checking/coercion
|
|
13
13
|
class_attribute :attributes_applied_via_resource
|
|
14
14
|
class_attribute :extra_attributes_applied_via_resource
|
|
15
|
+
class_attribute :relationship_condition_blocks
|
|
15
16
|
self.attributes_applied_via_resource = []
|
|
16
17
|
self.extra_attributes_applied_via_resource = []
|
|
18
|
+
# See #requested_relationships
|
|
19
|
+
self.relationship_condition_blocks ||= {}
|
|
17
20
|
|
|
18
21
|
def self.inherited(klass)
|
|
19
22
|
super
|
|
20
23
|
klass.class_eval do
|
|
21
24
|
extend JSONAPI::Serializable::Resource::ConditionalFields
|
|
25
|
+
|
|
26
|
+
# See #requested_relationships
|
|
27
|
+
def self.relationship(name, options = {}, &block)
|
|
28
|
+
super
|
|
29
|
+
field_condition_blocks.delete(name)
|
|
30
|
+
_register_condition(relationship_condition_blocks, name, options)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# NB - avoid clobbering includes when sparse fieldset
|
|
34
|
+
# https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
|
|
35
|
+
#
|
|
36
|
+
# We also override this method to ensure attributes and relationships
|
|
37
|
+
# have separate condition blocks. This way an attribute and
|
|
38
|
+
# relationship can have the same name, and the attribute can be
|
|
39
|
+
# conditional without affecting the relationship.
|
|
40
|
+
def requested_relationships(fields)
|
|
41
|
+
@_relationships.select do |k, _|
|
|
42
|
+
_conditionally_included?(self.class.relationship_condition_blocks, k)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
22
45
|
end
|
|
23
46
|
end
|
|
24
47
|
|
|
@@ -29,12 +52,6 @@ module Graphiti
|
|
|
29
52
|
end
|
|
30
53
|
end
|
|
31
54
|
|
|
32
|
-
# Temporary fix until fixed upstream
|
|
33
|
-
# https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
|
|
34
|
-
def requested_relationships(fields)
|
|
35
|
-
@_relationships
|
|
36
|
-
end
|
|
37
|
-
|
|
38
55
|
# Allow access to resource methods
|
|
39
56
|
def method_missing(id, *args, &blk)
|
|
40
57
|
if @resource.respond_to?(id, true)
|
data/lib/graphiti/sideload.rb
CHANGED
|
@@ -136,6 +136,14 @@ module Graphiti
|
|
|
136
136
|
base_filter(parents)
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
+
def link_extra_fields
|
|
140
|
+
extra_fields_name = [association_name, resource.type].find { |param|
|
|
141
|
+
context.params.dig(:extra_fields, param)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
{resource.type => context.params.dig(:extra_fields, extra_fields_name)} if extra_fields_name
|
|
145
|
+
end
|
|
146
|
+
|
|
139
147
|
# The parent resource is a remote,
|
|
140
148
|
# AND the sideload is a remote to the same endpoint
|
|
141
149
|
def shared_remote?
|
|
@@ -209,7 +217,9 @@ module Graphiti
|
|
|
209
217
|
end
|
|
210
218
|
|
|
211
219
|
with_error_handling(Errors::SideloadQueryBuildingError) do
|
|
212
|
-
|
|
220
|
+
scope = base_scope
|
|
221
|
+
scope[:foreign_key] = foreign_key if remote?
|
|
222
|
+
proxy = resource.class._all(params, opts, scope)
|
|
213
223
|
pre_load_proc&.call(proxy, parents)
|
|
214
224
|
end
|
|
215
225
|
|
data/lib/graphiti/util/class.rb
CHANGED
|
@@ -20,6 +20,12 @@ module Graphiti
|
|
|
20
20
|
split = namespace.split("::")
|
|
21
21
|
split[0, split.length - 1].join("::")
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
def self.graphql_type_name(name)
|
|
25
|
+
name.gsub("Resource", "")
|
|
26
|
+
.gsub("::", "") # remove modules
|
|
27
|
+
.gsub(".", "__") # remove remote resource .
|
|
28
|
+
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
25
31
|
end
|
data/lib/graphiti/util/link.rb
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
module Graphiti
|
|
3
3
|
module Util
|
|
4
4
|
class RemoteParams
|
|
5
|
-
def self.generate(resource, query)
|
|
6
|
-
new(resource, query).generate
|
|
5
|
+
def self.generate(resource, query, foreign_key)
|
|
6
|
+
new(resource, query, foreign_key).generate
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def initialize(resource, query)
|
|
9
|
+
def initialize(resource, query, foreign_key)
|
|
10
10
|
@resource = resource
|
|
11
11
|
@query = query
|
|
12
|
+
@foreign_key = foreign_key
|
|
12
13
|
@sorts = []
|
|
13
14
|
@filters = {}
|
|
14
15
|
@fields = {}
|
|
@@ -97,7 +98,11 @@ module Graphiti
|
|
|
97
98
|
return unless fields
|
|
98
99
|
|
|
99
100
|
fields.each_pair do |type, attrs|
|
|
100
|
-
|
|
101
|
+
all_attrs = attrs
|
|
102
|
+
if @foreign_key
|
|
103
|
+
all_attrs |= [@foreign_key]
|
|
104
|
+
end
|
|
105
|
+
@fields[type] = all_attrs.join(",")
|
|
101
106
|
end
|
|
102
107
|
end
|
|
103
108
|
|
|
@@ -44,6 +44,7 @@ module Graphiti
|
|
|
44
44
|
model.delete_field(:_relationships)
|
|
45
45
|
# If this isn't set, Array(resources) will return []
|
|
46
46
|
# This is important, because jsonapi-serializable makes this call
|
|
47
|
+
# To see the test failure, remote resource position > department
|
|
47
48
|
model.instance_variable_set(:@__graphiti_resource, 1)
|
|
48
49
|
model.instance_variable_set(:@__graphiti_serializer, serializer)
|
|
49
50
|
end
|
|
@@ -12,18 +12,20 @@ module Graphiti
|
|
|
12
12
|
def apply
|
|
13
13
|
return unless readable?
|
|
14
14
|
|
|
15
|
+
remove_guard if previously_guarded?
|
|
16
|
+
|
|
15
17
|
if @name == :id
|
|
16
18
|
@serializer.id(&proc)
|
|
17
19
|
elsif @attr[:proc]
|
|
18
20
|
@serializer.send(_method, @name, serializer_options, &proc)
|
|
19
21
|
elsif @serializer.attribute_blocks[@name].nil?
|
|
20
22
|
@serializer.send(_method, @name, serializer_options, &proc)
|
|
23
|
+
elsif @serializer.send(applied_method).include?(@name)
|
|
24
|
+
@serializer.field_condition_blocks[@name] = guard if guard?
|
|
21
25
|
else
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@serializer.send(_method, @name, serializer_options, &wrapped)
|
|
26
|
-
end
|
|
26
|
+
inner = @serializer.attribute_blocks.delete(@name)
|
|
27
|
+
wrapped = wrap_proc(inner)
|
|
28
|
+
@serializer.send(_method, @name, serializer_options, &wrapped)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
existing = @serializer.send(applied_method)
|
|
@@ -32,6 +34,14 @@ module Graphiti
|
|
|
32
34
|
|
|
33
35
|
private
|
|
34
36
|
|
|
37
|
+
def previously_guarded?
|
|
38
|
+
@serializer.field_condition_blocks[@name]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def remove_guard
|
|
42
|
+
@serializer.field_condition_blocks.delete(@name)
|
|
43
|
+
end
|
|
44
|
+
|
|
35
45
|
def applied_method
|
|
36
46
|
if extra?
|
|
37
47
|
:extra_attributes_applied_via_resource
|
|
@@ -56,16 +66,21 @@ module Graphiti
|
|
|
56
66
|
method_name = @attr[:readable]
|
|
57
67
|
instance = @resource.new
|
|
58
68
|
attribute = @name.to_s
|
|
69
|
+
resource_class = @resource
|
|
59
70
|
|
|
60
71
|
-> {
|
|
61
72
|
method = instance.method(method_name)
|
|
62
|
-
if method.arity.zero?
|
|
73
|
+
result = if method.arity.zero?
|
|
63
74
|
instance.instance_eval(&method_name)
|
|
64
75
|
elsif method.arity == 1
|
|
65
76
|
instance.instance_exec(@object, &method)
|
|
66
77
|
else
|
|
67
78
|
instance.instance_exec(@object, attribute, &method)
|
|
68
79
|
end
|
|
80
|
+
if Graphiti.context[:graphql] && !result
|
|
81
|
+
raise ::Graphiti::Errors::UnreadableAttribute.new(resource_class, attribute)
|
|
82
|
+
end
|
|
83
|
+
result
|
|
69
84
|
}
|
|
70
85
|
end
|
|
71
86
|
|
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.2.
|
|
4
|
+
version: 1.2.36
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lee Richmond
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-03-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jsonapi-serializable
|
|
@@ -308,6 +308,7 @@ files:
|
|
|
308
308
|
- lib/graphiti/scoping/default_filter.rb
|
|
309
309
|
- lib/graphiti/scoping/extra_attributes.rb
|
|
310
310
|
- lib/graphiti/scoping/filter.rb
|
|
311
|
+
- lib/graphiti/scoping/filter_group_validator.rb
|
|
311
312
|
- lib/graphiti/scoping/filterable.rb
|
|
312
313
|
- lib/graphiti/scoping/paginate.rb
|
|
313
314
|
- lib/graphiti/scoping/sort.rb
|