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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3914d079757237601c7908f6e1aa18ef2696eb95c5f5362b79c901da46c62676
4
- data.tar.gz: 203b947f0bad1ed2ce4fb8538aa5e7e79e7ad727e8a068655b4a0219bbf0adab
3
+ metadata.gz: 8657023a3ffabe32824165c3bca07a95a9cb24fd9043d75a773f464674edccf8
4
+ data.tar.gz: 6fe6875932185fa63ddde8e21545b55e5aea7bca78d8079455be1d77d1e9fa0e
5
5
  SHA512:
6
- metadata.gz: 8e10a7536f8ecd7c88f0c32b736927c6eccc6786327c4da65565a06862acf0ea221593758cc3d9a95702c5fc98f4463666690ed0be07d6d399e41657b0a84c2e
7
- data.tar.gz: 154f6a7cb34432fcf1b1361e8793123c2d4280838d4c91addc7f13c5736f6f351a9bba9cb4a2596550b4bc58fd98ab0e4985985d6724aaaed33532c483fb39a1
6
+ metadata.gz: 354df547792b57898e0ef53e47e89e9c0f73499371d54dcb42bddea256b481e6d460f3860f6801dcce99fdd922557225671caf4957cd7756cdaa1097312e3f44
7
+ data.tar.gz: 9a2a57b65a0fce0863cdf473fb87e48452b92e3e201a1797d90f19ccf363b43e8a8fca332910e6095eb0eb7b6416aa9e92d00b3a1dc00d42b528914047608911
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 1.x
2
+
3
+ ### master (unreleased)
4
+
5
+ <!-- ### [version (YYYY-MM-DD)](diff_link) -->
6
+ <!-- Breaking changes:-->
7
+ <!-- Features:-->
8
+ <!-- Fixes:-->
9
+ <!-- Misc:-->
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
- [View Official Documentation](https://www.graphiti.dev/guides/)
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
 
@@ -36,6 +36,7 @@ module Graphiti
36
36
  :not_match,
37
37
  ],
38
38
  uuid: [:eq, :not_eq],
39
+ enum: [:eq, :not_eq],
39
40
  integer_id: numerical_operators,
40
41
  integer: numerical_operators,
41
42
  big_decimal: numerical_operators,
@@ -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
@@ -10,7 +10,7 @@ module Graphiti
10
10
  end
11
11
 
12
12
  def message
13
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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 = <<~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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- <<~MSG
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
- if Graphiti::Types[att[:type]][:canonical_name] == :boolean
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)
@@ -90,7 +90,6 @@ module Graphiti
90
90
 
91
91
  [operator, value]
92
92
  end
93
-
94
93
  end
95
94
 
96
95
  def validate_operator(filter, operator)
@@ -184,14 +184,22 @@ module Graphiti
184
184
  end
185
185
 
186
186
  def load(parents, query, graph_parent)
187
- params = load_params(parents, query)
188
- params_proc&.call(params, parents)
189
- return [] if blank_query?(params)
190
- opts = load_options(parents, query)
191
- opts[:sideload] = self
192
- opts[:parent] = graph_parent
193
- proxy = resource.class._all(params, opts, base_scope)
194
- pre_load_proc&.call(proxy, parents)
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
- if self.class.assign_proc
366
- instance_exec(parents, children, &self.class.assign_proc)
367
- else
368
- assign(parents, children)
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)
@@ -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"],
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.0.rc.26"
2
+ VERSION = "1.0.rc.27"
3
3
  end
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.26
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-18 00:00:00.000000000 Z
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.6
337
+ rubygems_version: 2.7.9
337
338
  signing_key:
338
339
  specification_version: 4
339
340
  summary: Easily build jsonapi.org-compatible APIs