graphiti 1.2.31 → 1.3.4
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 +3 -0
- data/deprecated_generators/graphiti/generator_mixin.rb +1 -0
- data/lib/graphiti/adapters/abstract.rb +3 -2
- data/lib/graphiti/adapters/active_record.rb +7 -3
- data/lib/graphiti/adapters/graphiti_api.rb +1 -1
- data/lib/graphiti/adapters/null.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 +33 -10
- data/lib/graphiti/errors.rb +71 -11
- data/lib/graphiti/extensions/extra_attribute.rb +3 -3
- data/lib/graphiti/hash_renderer.rb +194 -21
- data/lib/graphiti/query.rb +32 -6
- data/lib/graphiti/renderer.rb +19 -1
- data/lib/graphiti/request_validators/update_validator.rb +3 -3
- data/lib/graphiti/request_validators/validator.rb +29 -21
- data/lib/graphiti/resource/configuration.rb +22 -1
- data/lib/graphiti/resource/dsl.rb +20 -2
- 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.rb +2 -1
- data/lib/graphiti/resource_proxy.rb +12 -0
- data/lib/graphiti/schema.rb +27 -4
- data/lib/graphiti/schema_diff.rb +44 -4
- data/lib/graphiti/scope.rb +2 -2
- data/lib/graphiti/scoping/filter.rb +19 -8
- data/lib/graphiti/scoping/filter_group_validator.rb +78 -0
- data/lib/graphiti/scoping/paginate.rb +47 -3
- data/lib/graphiti/serializer.rb +41 -6
- data/lib/graphiti/sideload.rb +16 -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 +37 -10
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +2 -0
- metadata +4 -3
data/lib/graphiti/query.rb
CHANGED
@@ -32,7 +32,9 @@ module Graphiti
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def pagination_links?
|
35
|
-
if
|
35
|
+
if action == :find
|
36
|
+
false
|
37
|
+
elsif Graphiti.config.pagination_links_on_demand
|
36
38
|
[true, "true"].include?(@params[:pagination_links])
|
37
39
|
else
|
38
40
|
Graphiti.config.pagination_links
|
@@ -96,7 +98,18 @@ module Graphiti
|
|
96
98
|
sl_resource = resource_for_sideload(sideload)
|
97
99
|
query_parents = parents + [self]
|
98
100
|
sub_hash = sub_hash[:include] if sub_hash.key?(:include)
|
99
|
-
|
101
|
+
|
102
|
+
# NB: To handle on__<type>--<name>
|
103
|
+
# A) relationship_name == :positions
|
104
|
+
# B) key == on__employees.positions
|
105
|
+
# This way A) ensures sideloads are resolved
|
106
|
+
# And B) ensures nested filters, sorts etc still work
|
107
|
+
relationship_name = sideload ? sideload.name : key
|
108
|
+
hash[relationship_name] = Query.new sl_resource,
|
109
|
+
@params,
|
110
|
+
key,
|
111
|
+
sub_hash,
|
112
|
+
query_parents, :all
|
100
113
|
else
|
101
114
|
handle_missing_sideload(key)
|
102
115
|
end
|
@@ -178,12 +191,13 @@ module Graphiti
|
|
178
191
|
(@params[:page] || {}).each_pair do |name, value|
|
179
192
|
if legacy_nested?(name)
|
180
193
|
value.each_pair do |k, v|
|
181
|
-
hash[k.to_sym] = v
|
194
|
+
hash[k.to_sym] = cast_page_param(k.to_sym, v)
|
182
195
|
end
|
183
196
|
elsif nested?(name)
|
184
|
-
|
185
|
-
|
186
|
-
|
197
|
+
param_name = name.to_s.split(".").last.to_sym
|
198
|
+
hash[param_name] = cast_page_param(param_name, value)
|
199
|
+
elsif top_level? && Scoping::Paginate::PARAMS.include?(name.to_sym)
|
200
|
+
hash[name.to_sym] = cast_page_param(name.to_sym, value)
|
187
201
|
end
|
188
202
|
end
|
189
203
|
end
|
@@ -227,6 +241,18 @@ module Graphiti
|
|
227
241
|
|
228
242
|
private
|
229
243
|
|
244
|
+
def cast_page_param(name, value)
|
245
|
+
if [:before, :after].include?(name)
|
246
|
+
decode_cursor(value)
|
247
|
+
else
|
248
|
+
value.to_i
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def decode_cursor(cursor)
|
253
|
+
JSON.parse(Base64.decode64(cursor)).symbolize_keys
|
254
|
+
end
|
255
|
+
|
230
256
|
# Try to find on this resource
|
231
257
|
# If not there, follow the legacy logic of scalling all other
|
232
258
|
# resource names/types
|
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)
|
@@ -49,6 +66,7 @@ module Graphiti
|
|
49
66
|
options[:meta] ||= proxy.meta
|
50
67
|
options[:meta][:stats] = proxy.stats unless proxy.stats.empty?
|
51
68
|
options[:meta][:debug] = Debugger.to_a if debug_json?
|
69
|
+
options[:proxy] = proxy
|
52
70
|
|
53
71
|
renderer.render(records, options)
|
54
72
|
end
|
@@ -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, @action, 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
|
@@ -62,15 +65,20 @@ module Graphiti
|
|
62
65
|
next
|
63
66
|
end
|
64
67
|
|
65
|
-
|
66
|
-
|
68
|
+
resource = x[:resource]
|
69
|
+
attributes = x[:attributes]
|
70
|
+
relationships = x[:relationships]
|
71
|
+
payload_path = x[:meta][:payload_path]
|
72
|
+
action = x[:meta][:method]
|
73
|
+
typecast_attributes(resource, attributes, action, payload_path)
|
74
|
+
process_relationships(resource, relationships, payload_path)
|
67
75
|
end
|
68
76
|
end
|
69
77
|
|
70
|
-
def typecast_attributes(resource, attributes, payload_path)
|
78
|
+
def typecast_attributes(resource, attributes, action, payload_path)
|
71
79
|
attributes.each_pair do |key, value|
|
72
80
|
# Only validate id if create action, otherwise it's only used for lookup
|
73
|
-
next if
|
81
|
+
next if action != :create &&
|
74
82
|
key == :id &&
|
75
83
|
resource.class.config[:attributes][:id][:writable] == false
|
76
84
|
|
@@ -86,8 +94,8 @@ module Graphiti
|
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
89
|
-
def normalized_params
|
90
|
-
normalized =
|
97
|
+
def normalized_params(raw_params)
|
98
|
+
normalized = raw_params
|
91
99
|
if normalized.respond_to?(:to_unsafe_h)
|
92
100
|
normalized = normalized.to_unsafe_h.deep_symbolize_keys
|
93
101
|
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,9 @@ 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,
|
96
|
+
:cursor_paginatable
|
87
97
|
|
88
98
|
class << self
|
89
99
|
prepend Overrides
|
@@ -97,6 +107,7 @@ module Graphiti
|
|
97
107
|
# re-assigning causes a new Class.new
|
98
108
|
klass.serializer = (klass.serializer || klass.infer_serializer_superclass)
|
99
109
|
klass.type ||= klass.infer_type
|
110
|
+
klass.graphql_entrypoint = klass.type.to_s.pluralize.to_sym
|
100
111
|
default(klass, :attributes_readable_by_default, true)
|
101
112
|
default(klass, :attributes_writable_by_default, true)
|
102
113
|
default(klass, :attributes_sortable_by_default, true)
|
@@ -144,6 +155,7 @@ module Graphiti
|
|
144
155
|
if (@abstract_class = val)
|
145
156
|
self.serializer = nil
|
146
157
|
self.type = nil
|
158
|
+
self.graphql_entrypoint = nil
|
147
159
|
end
|
148
160
|
end
|
149
161
|
|
@@ -188,6 +200,7 @@ module Graphiti
|
|
188
200
|
@config ||=
|
189
201
|
{
|
190
202
|
filters: {},
|
203
|
+
grouped_filters: {},
|
191
204
|
default_filters: {},
|
192
205
|
stats: {},
|
193
206
|
sort_all: nil,
|
@@ -224,6 +237,10 @@ module Graphiti
|
|
224
237
|
config[:filters]
|
225
238
|
end
|
226
239
|
|
240
|
+
def grouped_filters
|
241
|
+
config[:grouped_filters]
|
242
|
+
end
|
243
|
+
|
227
244
|
def sorts
|
228
245
|
config[:sorts]
|
229
246
|
end
|
@@ -262,6 +279,10 @@ module Graphiti
|
|
262
279
|
self.class.filters
|
263
280
|
end
|
264
281
|
|
282
|
+
def grouped_filters
|
283
|
+
self.class.grouped_filters
|
284
|
+
end
|
285
|
+
|
265
286
|
def sort_all
|
266
287
|
self.class.sort_all
|
267
288
|
end
|
@@ -9,7 +9,11 @@ module Graphiti
|
|
9
9
|
opts = args.extract_options!
|
10
10
|
type_override = args[0]
|
11
11
|
|
12
|
-
if (att =
|
12
|
+
if (att = (attributes[name] || extra_attributes[name]))
|
13
|
+
# We're opting in to filtering, so force this
|
14
|
+
# UNLESS the filter is guarded at the attribute level
|
15
|
+
att[:filterable] = true if att[:filterable] == false
|
16
|
+
|
13
17
|
aliases = [name, opts[:aliases]].flatten.compact
|
14
18
|
operators = FilterOperators.build(self, att[:type], opts, &blk)
|
15
19
|
|
@@ -23,6 +27,8 @@ module Graphiti
|
|
23
27
|
end
|
24
28
|
|
25
29
|
required = att[:filterable] == :required || !!opts[:required]
|
30
|
+
schema = !!opts[:via_attribute_dsl] ? att[:schema] : opts[:schema] != false
|
31
|
+
|
26
32
|
config[:filters][name.to_sym] = {
|
27
33
|
aliases: aliases,
|
28
34
|
name: name.to_sym,
|
@@ -32,6 +38,7 @@ module Graphiti
|
|
32
38
|
single: !!opts[:single],
|
33
39
|
dependencies: opts[:dependent],
|
34
40
|
required: required,
|
41
|
+
schema: schema,
|
35
42
|
operators: operators.to_hash,
|
36
43
|
allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
|
37
44
|
deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
|
@@ -44,6 +51,17 @@ module Graphiti
|
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
54
|
+
def filter_group(filter_names, *args)
|
55
|
+
opts = args.extract_options!
|
56
|
+
|
57
|
+
Scoping::FilterGroupValidator.raise_unless_filter_group_requirement_valid!(self, opts[:required])
|
58
|
+
|
59
|
+
config[:grouped_filters] = {
|
60
|
+
names: filter_names,
|
61
|
+
required: opts[:required]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
47
65
|
def sort_all(&blk)
|
48
66
|
if block_given?
|
49
67
|
config[:_sort_all] = blk
|
@@ -119,7 +137,7 @@ module Graphiti
|
|
119
137
|
options[:sortable] ? sort(name) : config[:sorts].delete(name)
|
120
138
|
|
121
139
|
if options[:filterable]
|
122
|
-
filter(name, allow: options[:allow])
|
140
|
+
filter(name, allow: options[:allow], via_attribute_dsl: true)
|
123
141
|
else
|
124
142
|
config[:filters].delete(name)
|
125
143
|
end
|
@@ -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
|
data/lib/graphiti/resource.rb
CHANGED
@@ -28,11 +28,12 @@ module Graphiti
|
|
28
28
|
serializer
|
29
29
|
end
|
30
30
|
|
31
|
-
def decorate_record(record)
|
31
|
+
def decorate_record(record, index = nil)
|
32
32
|
unless record.instance_variable_get(:@__graphiti_serializer)
|
33
33
|
serializer = serializer_for(record)
|
34
34
|
record.instance_variable_set(:@__graphiti_serializer, serializer)
|
35
35
|
record.instance_variable_set(:@__graphiti_resource, self)
|
36
|
+
record.instance_variable_set(:@__graphiti_index, index) if index
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -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|
|
@@ -182,7 +197,7 @@ module Graphiti
|
|
182
197
|
def filters(resource)
|
183
198
|
{}.tap do |f|
|
184
199
|
resource.filters.each_pair do |name, filter|
|
185
|
-
next unless resource.
|
200
|
+
next unless resource.filters[name][:schema]
|
186
201
|
|
187
202
|
config = {
|
188
203
|
type: filter[:type].to_s,
|
@@ -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
@@ -1,8 +1,8 @@
|
|
1
1
|
module Graphiti
|
2
2
|
class SchemaDiff
|
3
3
|
def initialize(old, new)
|
4
|
-
@old = old.deep_symbolize_keys
|
5
|
-
@new = new.deep_symbolize_keys
|
4
|
+
@old = JSON.parse(old.to_json).deep_symbolize_keys
|
5
|
+
@new = JSON.parse(new.to_json).deep_symbolize_keys
|
6
6
|
@errors = []
|
7
7
|
end
|
8
8
|
|
@@ -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
|
@@ -133,12 +135,12 @@ module Graphiti
|
|
133
135
|
end
|
134
136
|
|
135
137
|
if new_sort[:only] && !old_sort[:only]
|
136
|
-
@errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].inspect}."
|
138
|
+
@errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].to_sym.inspect}."
|
137
139
|
end
|
138
140
|
|
139
141
|
if new_sort[:only] && old_sort[:only]
|
140
142
|
if new_sort[:only] != old_sort[:only]
|
141
|
-
@errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].inspect}, now limited to only #{new_sort[:only].inspect}."
|
143
|
+
@errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].to_sym.inspect}, now limited to only #{new_sort[:only].to_sym.inspect}."
|
142
144
|
end
|
143
145
|
end
|
144
146
|
end
|
@@ -204,6 +206,44 @@ 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.map(&:to_sym).inspect} added #{"member".pluralize(diff.length)} #{diff.map(&:to_sym).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.map(&:to_sym).inspect} moved from required: :any to required: :all"
|
223
|
+
end
|
224
|
+
else
|
225
|
+
@errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].map(&:to_sym).inspect} was added."
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def compare_stats(old_resource, new_resource)
|
231
|
+
return unless old_resource.key?(:stats)
|
232
|
+
|
233
|
+
old_resource[:stats].each_pair do |name, old_calculations|
|
234
|
+
new_calculations = new_resource[:stats][name]
|
235
|
+
if new_calculations
|
236
|
+
old_calculations.each do |calc|
|
237
|
+
unless new_calculations.include?(calc)
|
238
|
+
@errors << "#{old_resource[:name]}: calculation #{calc.to_sym.inspect} was removed from stat #{name.inspect}."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
@errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
207
247
|
def compare_endpoints
|
208
248
|
@old[:endpoints].each_pair do |path, old_endpoint|
|
209
249
|
unless (new_endpoint = @new[:endpoints][path])
|
data/lib/graphiti/scope.rb
CHANGED
@@ -85,8 +85,8 @@ module Graphiti
|
|
85
85
|
# Used to ensure the resource's serializer is used
|
86
86
|
# Not one derived through the usual jsonapi-rb logic
|
87
87
|
def assign_serializer(records)
|
88
|
-
records.
|
89
|
-
@resource.decorate_record(r)
|
88
|
+
records.each_with_index do |r, index|
|
89
|
+
@resource.decorate_record(r, index)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -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
|
@@ -164,14 +171,18 @@ module Graphiti
|
|
164
171
|
type = Graphiti::Types[filter[:type]]
|
165
172
|
array_or_string = [:string, :array].include?(type[:canonical_name])
|
166
173
|
if (arr = value.scan(/\[.*?\]/)).present? && array_or_string
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
174
|
+
begin
|
175
|
+
value = arr.map { |json|
|
176
|
+
begin
|
177
|
+
JSON.parse(json)
|
178
|
+
rescue
|
179
|
+
raise Errors::InvalidJSONArray.new(resource, value)
|
180
|
+
end
|
181
|
+
}
|
182
|
+
value = value[0] if value.length == 1
|
183
|
+
rescue Errors::InvalidJSONArray => e
|
184
|
+
raise(e) if type[:canonical_name] == :array
|
185
|
+
end
|
175
186
|
else
|
176
187
|
value = parse_string_arrays(value, !!filter[:single])
|
177
188
|
end
|