graphiti 1.0.rc.26 → 1.0.rc.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +9 -3
- data/lib/graphiti/adapters/abstract.rb +1 -0
- data/lib/graphiti/adapters/active_record.rb +2 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +11 -0
- data/lib/graphiti/errors.rb +107 -15
- data/lib/graphiti/resource/dsl.rb +13 -3
- data/lib/graphiti/scoping/filter.rb +0 -1
- data/lib/graphiti/sideload.rb +31 -12
- data/lib/graphiti/types.rb +16 -0
- data/lib/graphiti/util/link.rb +3 -0
- data/lib/graphiti/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8657023a3ffabe32824165c3bca07a95a9cb24fd9043d75a773f464674edccf8
|
4
|
+
data.tar.gz: 6fe6875932185fa63ddde8e21545b55e5aea7bca78d8079455be1d77d1e9fa0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 354df547792b57898e0ef53e47e89e9c0f73499371d54dcb42bddea256b481e6d460f3860f6801dcce99fdd922557225671caf4957cd7756cdaa1097312e3f44
|
7
|
+
data.tar.gz: 9a2a57b65a0fce0863cdf473fb87e48452b92e3e201a1797d90f19ccf363b43e8a8fca332910e6095eb0eb7b6416aa9e92d00b3a1dc00d42b528914047608911
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
|
-
### Graphiti
|
2
|
-
|
3
1
|
![Build Status](https://travis-ci.org/graphiti-api/graphiti.svg?branch=master)
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/graphiti.svg)](https://badge.fury.io/rb/graphiti)
|
3
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
4
4
|
|
5
|
+
<p align="center">
|
6
|
+
<a href="https://www.graphiti.dev/guides">
|
7
|
+
<img " src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" />
|
8
|
+
</a>
|
9
|
+
</p>
|
10
|
+
|
5
11
|
Stylish Graph APIs.
|
6
12
|
|
7
|
-
[
|
13
|
+
[Website](https://www.graphiti.dev/guides/)
|
8
14
|
|
9
15
|
[Join the Slack Channel](https://join.slack.com/t/graphiti-api/shared_invite/enQtMjkyMTA3MDgxNTQzLWVkMDM3NTlmNTIwODY2YWFkMGNiNzUzZGMzOTY3YmNmZjBhYzIyZWZlZTk4YmI1YTI0Y2M0OTZmZGYwN2QxZjg)
|
10
16
|
|
@@ -25,6 +25,7 @@ module Graphiti
|
|
25
25
|
alias filter_date_eq filter_eq
|
26
26
|
alias filter_boolean_eq filter_eq
|
27
27
|
alias filter_uuid_eq filter_eq
|
28
|
+
alias filter_enum_eq filter_eq
|
28
29
|
|
29
30
|
def filter_not_eq(scope, attribute, value)
|
30
31
|
scope.where.not(attribute => value)
|
@@ -35,6 +36,7 @@ module Graphiti
|
|
35
36
|
alias filter_date_not_eq filter_not_eq
|
36
37
|
alias filter_boolean_not_eq filter_not_eq
|
37
38
|
alias filter_uuid_not_eq filter_not_eq
|
39
|
+
alias filter_enum_not_eq filter_not_eq
|
38
40
|
|
39
41
|
def filter_string_eq(scope, attribute, value, is_not: false)
|
40
42
|
column = column_for(scope, attribute)
|
@@ -5,9 +5,20 @@ module Graphiti::Adapters::ActiveRecord::Inferrence
|
|
5
5
|
parent_model = parent_resource_class.model
|
6
6
|
reflection = parent_model.reflections[association_name.to_s]
|
7
7
|
if reflection
|
8
|
+
reflection = proper_reflection(reflection)
|
8
9
|
reflection.foreign_key.to_sym
|
9
10
|
else
|
10
11
|
super
|
11
12
|
end
|
12
13
|
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def proper_reflection(reflection)
|
18
|
+
if (thru = reflection.through_reflection)
|
19
|
+
proper_reflection(thru)
|
20
|
+
else
|
21
|
+
reflection
|
22
|
+
end
|
23
|
+
end
|
13
24
|
end
|
data/lib/graphiti/errors.rb
CHANGED
@@ -10,7 +10,7 @@ module Graphiti
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def message
|
13
|
-
|
13
|
+
<<-MSG
|
14
14
|
The adapter #{@adapter.class} does not implement method '#{@method}', which was requested for attribute '#{@attribute}'. Add this method to your adapter to support this filter operator.
|
15
15
|
MSG
|
16
16
|
end
|
@@ -24,7 +24,7 @@ module Graphiti
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def message
|
27
|
-
|
27
|
+
<<-MSG
|
28
28
|
#{@parent_resource_class} sideload :#{@name} - #{@message}
|
29
29
|
MSG
|
30
30
|
end
|
@@ -53,7 +53,7 @@ module Graphiti
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def message
|
56
|
-
|
56
|
+
<<-MSG
|
57
57
|
#{@resource_class}: Tried to pass block to .#{@method_name}, which only accepts a method name.
|
58
58
|
MSG
|
59
59
|
end
|
@@ -65,7 +65,7 @@ module Graphiti
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def message
|
68
|
-
|
68
|
+
<<-MSG
|
69
69
|
#{@resource_class}: Tried to perform write operation. Writes are not supported for remote resources - hit the endpoint directly.
|
70
70
|
MSG
|
71
71
|
end
|
@@ -80,7 +80,7 @@ module Graphiti
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def message
|
83
|
-
|
83
|
+
<<-MSG
|
84
84
|
#{@resource.class}: Tried to filter #{@filter_name.inspect} on operator #{@operator.inspect}, but not supported! Supported operators are #{@supported}.
|
85
85
|
MSG
|
86
86
|
end
|
@@ -93,7 +93,7 @@ module Graphiti
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def message
|
96
|
-
|
96
|
+
<<-MSG
|
97
97
|
#{@resource.class}: Tried to persist association #{@sideload.name.inspect} but marked writable: false
|
98
98
|
MSG
|
99
99
|
end
|
@@ -106,7 +106,7 @@ module Graphiti
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def message
|
109
|
-
|
109
|
+
<<-MSG
|
110
110
|
#{@sideload.parent_resource.class.name}: tried to sideload #{@sideload.name.inspect}, but more than one #{@sideload.parent_resource.model.name} was passed!
|
111
111
|
|
112
112
|
This is because you marked the sideload #{@sideload.name.inspect} with single: true
|
@@ -127,7 +127,7 @@ module Graphiti
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def message
|
130
|
-
|
130
|
+
<<-MSG
|
131
131
|
#{@resource.class.name}: tried to sort on attribute #{@attribute.inspect}, but passed #{@direction.inspect} when only #{@allowlist.inspect} is supported.
|
132
132
|
MSG
|
133
133
|
end
|
@@ -140,7 +140,7 @@ module Graphiti
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def message
|
143
|
-
|
143
|
+
<<-MSG
|
144
144
|
#{@resource_class.name}: called .on_extra_attribute #{@name.inspect}, but extra attribute #{@name.inspect} does not exist!
|
145
145
|
MSG
|
146
146
|
end
|
@@ -156,7 +156,7 @@ module Graphiti
|
|
156
156
|
def message
|
157
157
|
allow = @filter.values[0][:allow]
|
158
158
|
deny = @filter.values[0][:deny]
|
159
|
-
msg =
|
159
|
+
msg = <<-MSG
|
160
160
|
#{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid value #{@value.inspect}.
|
161
161
|
MSG
|
162
162
|
msg << "\nAllowlist: #{allow.inspect}" if allow
|
@@ -165,6 +165,30 @@ module Graphiti
|
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
168
|
+
class MissingEnumAllowList < Base
|
169
|
+
def initialize(resource_class, filter_name, enum_type)
|
170
|
+
@resource_class = resource_class
|
171
|
+
@filter_name = filter_name
|
172
|
+
@enum_type = enum_type
|
173
|
+
end
|
174
|
+
|
175
|
+
def message
|
176
|
+
<<-MSG
|
177
|
+
#{@resource_class.name} You declared an attribute or filter of type "#{@enum_type}" without providing a list of permitted values, which is required.
|
178
|
+
|
179
|
+
When declaring an attribute:
|
180
|
+
|
181
|
+
attribute :status, :#{@enum_type}, allow: ['published', 'draft']
|
182
|
+
|
183
|
+
When declaring a filter:
|
184
|
+
|
185
|
+
filter :status, :#{@enum_type}, allow: ['published', 'draft'] do
|
186
|
+
# ...
|
187
|
+
end
|
188
|
+
MSG
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
168
192
|
class InvalidLink < Base
|
169
193
|
def initialize(resource_class, sideload, action)
|
170
194
|
@resource_class = resource_class
|
@@ -173,7 +197,7 @@ module Graphiti
|
|
173
197
|
end
|
174
198
|
|
175
199
|
def message
|
176
|
-
|
200
|
+
<<-MSG
|
177
201
|
#{@resource_class.name}: Cannot link to sideload #{@sideload.name.inspect}!
|
178
202
|
|
179
203
|
Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
|
@@ -191,7 +215,7 @@ module Graphiti
|
|
191
215
|
end
|
192
216
|
|
193
217
|
def message
|
194
|
-
|
218
|
+
<<-MSG
|
195
219
|
#{@resource.class.name}: passed multiple values to filter #{@filter.keys[0].inspect}, which was marked single: true.
|
196
220
|
|
197
221
|
Value was: #{@value.inspect}
|
@@ -206,7 +230,7 @@ module Graphiti
|
|
206
230
|
end
|
207
231
|
|
208
232
|
def message
|
209
|
-
|
233
|
+
<<-MSG
|
210
234
|
#{@resource_class.name}: Tried to link sideload #{@sideload.name.inspect}, but cannot generate links!
|
211
235
|
|
212
236
|
Graphiti.config.context_for_endpoint must be set to enable link generation:
|
@@ -216,6 +240,74 @@ module Graphiti
|
|
216
240
|
end
|
217
241
|
end
|
218
242
|
|
243
|
+
class SideloadParamsError < Base
|
244
|
+
def initialize(resource_class, sideload_name)
|
245
|
+
@resource_class = resource_class
|
246
|
+
@sideload_name = sideload_name
|
247
|
+
end
|
248
|
+
|
249
|
+
def message
|
250
|
+
<<-MSG
|
251
|
+
#{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
|
252
|
+
|
253
|
+
The error was raised while attempting to build query parameters for the associated Resource.
|
254
|
+
Read more about sideload scoping here: www.graphiti.dev/guides/concepts/resources#customizing-scope
|
255
|
+
|
256
|
+
A good way to debug is to put a debugger within the 'params' block.
|
257
|
+
|
258
|
+
Here's the original, underlying error:
|
259
|
+
|
260
|
+
#{cause.class.name}: #{cause}
|
261
|
+
#{cause.backtrace.join("\n")}
|
262
|
+
MSG
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class SideloadQueryBuildingError < Base
|
267
|
+
def initialize(resource_class, sideload_name)
|
268
|
+
@resource_class = resource_class
|
269
|
+
@sideload_name = sideload_name
|
270
|
+
end
|
271
|
+
|
272
|
+
def message
|
273
|
+
<<-MSG
|
274
|
+
#{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
|
275
|
+
|
276
|
+
The error was raised while attempting to build the scope for the associated Resource.
|
277
|
+
|
278
|
+
Read more about sideload scoping here: www.graphiti.dev/guides/concepts/resources#customizing-scope
|
279
|
+
|
280
|
+
Here's the original, underlying error:
|
281
|
+
|
282
|
+
#{cause.class.name}: #{cause.message}
|
283
|
+
#{cause.backtrace.join("\n")}
|
284
|
+
MSG
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class SideloadAssignError < Base
|
289
|
+
def initialize(resource_class, sideload_name)
|
290
|
+
@resource_class = resource_class
|
291
|
+
@sideload_name = sideload_name
|
292
|
+
end
|
293
|
+
|
294
|
+
def message
|
295
|
+
<<-MSG
|
296
|
+
#{@resource_class.name}: error occurred while sideloading "#{@sideload_name}"!
|
297
|
+
|
298
|
+
The error was raised while attempting to assign relevant model instances. Read
|
299
|
+
more about sideload assignment here: www.graphiti.dev/guides/concepts/resources#customizing-assignment
|
300
|
+
|
301
|
+
A good way to debug is to put a debugger within the 'assign' block.
|
302
|
+
|
303
|
+
Here's the original, underlying error:
|
304
|
+
|
305
|
+
#{cause.class.name}: #{cause.message}
|
306
|
+
#{cause.backtrace.join("\n")}
|
307
|
+
MSG
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
219
311
|
class AttributeError < Base
|
220
312
|
attr_reader :resource,
|
221
313
|
:name,
|
@@ -282,7 +374,7 @@ module Graphiti
|
|
282
374
|
end
|
283
375
|
|
284
376
|
def message
|
285
|
-
|
377
|
+
<<-MSG
|
286
378
|
#{@resource.class.name}: passed filter with value #{@value.inspect}, and failed attempting to parse as JSON array.
|
287
379
|
MSG
|
288
380
|
end
|
@@ -296,7 +388,7 @@ module Graphiti
|
|
296
388
|
end
|
297
389
|
|
298
390
|
def message
|
299
|
-
|
391
|
+
<<-MSG
|
300
392
|
#{@resource_class.name} cannot be called directly from endpoint #{@path}##{@action}!
|
301
393
|
|
302
394
|
Either set a primary endpoint for this resource:
|
@@ -12,8 +12,13 @@ module Graphiti
|
|
12
12
|
aliases = [name, opts[:aliases]].flatten.compact
|
13
13
|
operators = FilterOperators.build(self, att[:type], opts, &blk)
|
14
14
|
|
15
|
-
|
15
|
+
case Graphiti::Types[att[:type]][:canonical_name]
|
16
|
+
when :boolean
|
16
17
|
opts[:single] = true
|
18
|
+
when :enum
|
19
|
+
if opts[:allow].blank?
|
20
|
+
raise Errors::MissingEnumAllowList.new(self, name, att[:type])
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
required = att[:filterable] == :required || !!opts[:required]
|
@@ -29,7 +34,7 @@ module Graphiti
|
|
29
34
|
operators: operators.to_hash,
|
30
35
|
}
|
31
36
|
elsif (type = args[0])
|
32
|
-
attribute name, type, only: [:filterable]
|
37
|
+
attribute name, type, only: [:filterable], allow: opts[:allow]
|
33
38
|
filter(name, opts, &blk)
|
34
39
|
else
|
35
40
|
raise Errors::ImplicitFilterTypeMissing.new(self, name)
|
@@ -100,8 +105,13 @@ module Graphiti
|
|
100
105
|
options[:proc] = blk
|
101
106
|
config[:attributes][name] = options
|
102
107
|
apply_attributes_to_serializer
|
103
|
-
options[:filterable] ? filter(name) : config[:filters].delete(name)
|
104
108
|
options[:sortable] ? sort(name) : config[:sorts].delete(name)
|
109
|
+
|
110
|
+
if options[:filterable]
|
111
|
+
filter(name, allow: options[:allow])
|
112
|
+
else
|
113
|
+
config[:filters].delete(name)
|
114
|
+
end
|
105
115
|
end
|
106
116
|
|
107
117
|
def extra_attribute(name, type, options = {}, &blk)
|
data/lib/graphiti/sideload.rb
CHANGED
@@ -184,14 +184,22 @@ module Graphiti
|
|
184
184
|
end
|
185
185
|
|
186
186
|
def load(parents, query, graph_parent)
|
187
|
-
params =
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
187
|
+
params, opts, proxy = nil, nil, nil
|
188
|
+
|
189
|
+
with_error_handling Errors::SideloadParamsError do
|
190
|
+
params = load_params(parents, query)
|
191
|
+
params_proc&.call(params, parents)
|
192
|
+
return [] if blank_query?(params)
|
193
|
+
opts = load_options(parents, query)
|
194
|
+
opts[:sideload] = self
|
195
|
+
opts[:parent] = graph_parent
|
196
|
+
end
|
197
|
+
|
198
|
+
with_error_handling(Errors::SideloadQueryBuildingError) do
|
199
|
+
proxy = resource.class._all(params, opts, base_scope)
|
200
|
+
pre_load_proc&.call(proxy, parents)
|
201
|
+
end
|
202
|
+
|
195
203
|
proxy.to_a
|
196
204
|
end
|
197
205
|
|
@@ -362,11 +370,22 @@ module Graphiti
|
|
362
370
|
end
|
363
371
|
|
364
372
|
def fire_assign(parents, children)
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
373
|
+
with_error_handling Errors::SideloadAssignError do
|
374
|
+
if self.class.assign_proc
|
375
|
+
instance_exec(parents, children, &self.class.assign_proc)
|
376
|
+
else
|
377
|
+
assign(parents, children)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def with_error_handling(error_class)
|
383
|
+
begin
|
384
|
+
result = yield
|
385
|
+
rescue
|
386
|
+
raise error_class.new(parent_resource_class, name)
|
369
387
|
end
|
388
|
+
result
|
370
389
|
end
|
371
390
|
|
372
391
|
def fire_scope(parents)
|
data/lib/graphiti/types.rb
CHANGED
@@ -101,6 +101,22 @@ module Graphiti
|
|
101
101
|
kind: "scalar",
|
102
102
|
description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.",
|
103
103
|
},
|
104
|
+
string_enum: {
|
105
|
+
canonical_name: :enum,
|
106
|
+
params: Dry::Types["coercible.string"],
|
107
|
+
read: Dry::Types["coercible.string"],
|
108
|
+
write: Dry::Types["coercible.string"],
|
109
|
+
kind: "scalar",
|
110
|
+
description: "String enum type. Like a normal string, but only eq/!eq and case-sensitive. Limited to only the allowed values.",
|
111
|
+
},
|
112
|
+
integer_enum: {
|
113
|
+
canonical_name: :enum,
|
114
|
+
params: Dry::Types["coercible.integer"],
|
115
|
+
read: Dry::Types["coercible.integer"],
|
116
|
+
write: Dry::Types["coercible.integer"],
|
117
|
+
kind: "scalar",
|
118
|
+
description: "Integer enum type. Like a normal integer, but only eq/!eq filters. Limited to only the allowed values.",
|
119
|
+
},
|
104
120
|
string: {
|
105
121
|
params: Dry::Types["coercible.string"],
|
106
122
|
read: Dry::Types["coercible.string"],
|
data/lib/graphiti/util/link.rb
CHANGED
@@ -11,6 +11,7 @@ module Graphiti
|
|
11
11
|
@sideload = @sideload.children.values.find { |c|
|
12
12
|
c.group_name == type.to_sym
|
13
13
|
}
|
14
|
+
@polymorphic_sideload_not_found = true unless @sideload
|
14
15
|
else
|
15
16
|
@linkable = false
|
16
17
|
end
|
@@ -26,6 +27,8 @@ module Graphiti
|
|
26
27
|
private
|
27
28
|
|
28
29
|
def linkable?
|
30
|
+
return false if @polymorphic_sideload_not_found
|
31
|
+
|
29
32
|
if @sideload.type == :belongs_to
|
30
33
|
!@model.send(@sideload.foreign_key).nil?
|
31
34
|
else
|
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.rc.
|
4
|
+
version: 1.0.rc.27
|
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-03-
|
11
|
+
date: 2019-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jsonapi-serializable
|
@@ -204,6 +204,7 @@ files:
|
|
204
204
|
- ".travis.yml"
|
205
205
|
- ".yardopts"
|
206
206
|
- Appraisals
|
207
|
+
- CHANGELOG.md
|
207
208
|
- CODE_OF_CONDUCT.md
|
208
209
|
- Gemfile
|
209
210
|
- Guardfile
|
@@ -333,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
333
334
|
version: 1.3.1
|
334
335
|
requirements: []
|
335
336
|
rubyforge_project:
|
336
|
-
rubygems_version: 2.7.
|
337
|
+
rubygems_version: 2.7.9
|
337
338
|
signing_key:
|
338
339
|
specification_version: 4
|
339
340
|
summary: Easily build jsonapi.org-compatible APIs
|