graphiti 1.2.16 → 1.2.17

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +43 -14
  3. data/Appraisals +37 -5
  4. data/Guardfile +4 -4
  5. data/deprecated_generators/graphiti/resource_generator.rb +1 -1
  6. data/gemfiles/rails_5_0.gemfile +18 -0
  7. data/gemfiles/rails_5_0_graphiti_rails.gemfile +20 -0
  8. data/gemfiles/rails_5_1.gemfile +18 -0
  9. data/gemfiles/rails_5_1_graphiti_rails.gemfile +20 -0
  10. data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +0 -0
  11. data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +0 -0
  12. data/gemfiles/rails_6.gemfile +1 -1
  13. data/gemfiles/rails_6_graphiti_rails.gemfile +1 -1
  14. data/graphiti.gemspec +10 -10
  15. data/lib/graphiti.rb +3 -3
  16. data/lib/graphiti/adapters/abstract.rb +3 -3
  17. data/lib/graphiti/adapters/active_record.rb +64 -35
  18. data/lib/graphiti/configuration.rb +1 -1
  19. data/lib/graphiti/debugger.rb +4 -4
  20. data/lib/graphiti/delegates/pagination.rb +3 -3
  21. data/lib/graphiti/deserializer.rb +3 -3
  22. data/lib/graphiti/errors.rb +24 -4
  23. data/lib/graphiti/query.rb +4 -4
  24. data/lib/graphiti/railtie.rb +1 -1
  25. data/lib/graphiti/request_validator.rb +4 -4
  26. data/lib/graphiti/request_validators/update_validator.rb +1 -2
  27. data/lib/graphiti/request_validators/validator.rb +2 -2
  28. data/lib/graphiti/resource/configuration.rb +6 -4
  29. data/lib/graphiti/resource/dsl.rb +5 -4
  30. data/lib/graphiti/resource/links.rb +3 -3
  31. data/lib/graphiti/resource/persistence.rb +2 -1
  32. data/lib/graphiti/resource/polymorphism.rb +2 -1
  33. data/lib/graphiti/resource/remote.rb +1 -1
  34. data/lib/graphiti/runner.rb +1 -1
  35. data/lib/graphiti/schema.rb +6 -6
  36. data/lib/graphiti/scope.rb +5 -5
  37. data/lib/graphiti/scoping/base.rb +3 -3
  38. data/lib/graphiti/scoping/filter.rb +17 -7
  39. data/lib/graphiti/scoping/sort.rb +1 -1
  40. data/lib/graphiti/sideload.rb +26 -22
  41. data/lib/graphiti/sideload/belongs_to.rb +1 -1
  42. data/lib/graphiti/stats/payload.rb +4 -4
  43. data/lib/graphiti/types.rb +15 -15
  44. data/lib/graphiti/util/link.rb +5 -1
  45. data/lib/graphiti/util/persistence.rb +16 -10
  46. data/lib/graphiti/util/relationship_payload.rb +4 -4
  47. data/lib/graphiti/util/simple_errors.rb +1 -1
  48. data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
  49. data/lib/graphiti/version.rb +1 -1
  50. metadata +8 -4
@@ -25,9 +25,9 @@ module Graphiti
25
25
  # @param [Hash] opts configuration options used by subclasses
26
26
  def initialize(resource, query_hash, scope, opts = {})
27
27
  @query_hash = query_hash
28
- @resource = resource
29
- @scope = scope
30
- @opts = opts
28
+ @resource = resource
29
+ @scope = scope
30
+ @opts = opts
31
31
  end
32
32
 
33
33
  # Apply this scoping criteria.
@@ -31,7 +31,7 @@ module Graphiti
31
31
 
32
32
  def filter_via_adapter(filter, operator, value)
33
33
  type_name = Types.name_for(filter.values.first[:type])
34
- method = :"filter_#{type_name}_#{operator}"
34
+ method = :"filter_#{type_name}_#{operator}"
35
35
  attribute = filter.keys.first
36
36
 
37
37
  if resource.adapter.respond_to?(method)
@@ -54,6 +54,8 @@ module Graphiti
54
54
  unless type[:canonical_name] == :hash || !value.is_a?(String)
55
55
  value = parse_string_value(filter.values[0], value)
56
56
  end
57
+
58
+ check_deny_empty_filters!(resource, filter, value)
57
59
  value = parse_string_null(filter.values[0], value)
58
60
  validate_singular(resource, filter, value)
59
61
  value = coerce_types(filter.values[0], param_name.to_sym, value)
@@ -82,11 +84,11 @@ module Graphiti
82
84
  type = Types[filter.values[0][:type]][:canonical_name]
83
85
  if param_value.is_a?(Hash) && type == :hash
84
86
  operators_keys = filter.values[0][:operators].keys
85
- unless param_value.keys.all? {|k| operators_keys.include?(k)}
86
- param_value = { eq: param_value }
87
+ unless param_value.keys.all? { |k| operators_keys.include?(k) }
88
+ param_value = {eq: param_value}
87
89
  end
88
90
  elsif !param_value.is_a?(Hash) || param_value.empty?
89
- param_value = { eq: param_value }
91
+ param_value = {eq: param_value}
90
92
  end
91
93
 
92
94
  param_value.map do |operator, value|
@@ -184,10 +186,10 @@ module Graphiti
184
186
  # remove the quote characters from the quoted strings
185
187
  quotes.each { |q| q.gsub!("{{", "").gsub!("}}", "") }
186
188
  # merge everything back together into an array
187
- if singular_filter
188
- value = Array(value) + quotes
189
+ value = if singular_filter
190
+ Array(value) + quotes
189
191
  else
190
- value = Array(value.split(",")) + quotes
192
+ Array(value.split(",")) + quotes
191
193
  end
192
194
  # remove any blanks that are left
193
195
  value.reject! { |v| v.length.zero? }
@@ -200,5 +202,13 @@ module Graphiti
200
202
 
201
203
  value
202
204
  end
205
+
206
+ def check_deny_empty_filters!(resource, filter, value)
207
+ return unless filter.values[0][:deny_empty]
208
+
209
+ if value.nil? || value.empty? || value == "null"
210
+ raise Errors::InvalidFilterValue.new(resource, filter, "(empty)")
211
+ end
212
+ end
203
213
  end
204
214
  end
@@ -78,7 +78,7 @@ module Graphiti
78
78
 
79
79
  def sort_hash(attr)
80
80
  value = attr[0] == "-" ? :desc : :asc
81
- key = attr.sub("-", "").to_sym
81
+ key = attr.sub("-", "").to_sym
82
82
 
83
83
  {key => value}
84
84
  end
@@ -18,31 +18,31 @@ module Graphiti
18
18
  :link_proc
19
19
 
20
20
  def initialize(name, opts)
21
- @name = name
21
+ @name = name
22
22
  validate_options!(opts)
23
- @parent_resource_class = opts[:parent_resource]
24
- @resource_class = opts[:resource]
25
- @primary_key = opts[:primary_key]
26
- @foreign_key = opts[:foreign_key]
27
- @type = opts[:type]
28
- @base_scope = opts[:base_scope]
29
- @readable = evaluate_flag(opts[:readable])
30
- @writable = evaluate_flag(opts[:writable])
31
- @as = opts[:as]
32
- @link = opts[:link]
33
- @single = opts[:single]
34
- @remote = opts[:remote]
23
+ @parent_resource_class = opts[:parent_resource]
24
+ @resource_class = opts[:resource]
25
+ @primary_key = opts[:primary_key]
26
+ @foreign_key = opts[:foreign_key]
27
+ @type = opts[:type]
28
+ @base_scope = opts[:base_scope]
29
+ @readable = evaluate_flag(opts[:readable])
30
+ @writable = evaluate_flag(opts[:writable])
31
+ @as = opts[:as]
32
+ @link = opts[:link]
33
+ @single = opts[:single]
34
+ @remote = opts[:remote]
35
35
  apply_belongs_to_many_filter if type == :many_to_many
36
36
 
37
- @description = opts[:description]
37
+ @description = opts[:description]
38
38
 
39
39
  # polymorphic has_many
40
- @polymorphic_as = opts[:polymorphic_as]
40
+ @polymorphic_as = opts[:polymorphic_as]
41
41
  # polymorphic_belongs_to-specific
42
- @group_name = opts[:group_name]
43
- @polymorphic_child = opts[:polymorphic_child]
44
- @parent = opts[:parent]
45
- @always_include_resource_ids = opts[:always_include_resource_ids]
42
+ @group_name = opts[:group_name]
43
+ @polymorphic_child = opts[:polymorphic_child]
44
+ @parent = opts[:parent]
45
+ @always_include_resource_ids = opts[:always_include_resource_ids]
46
46
 
47
47
  if polymorphic_child?
48
48
  parent.resource.polymorphic << resource_class
@@ -197,7 +197,7 @@ module Graphiti
197
197
 
198
198
  with_error_handling Errors::SideloadParamsError do
199
199
  params = load_params(parents, query)
200
- params_proc&.call(params, parents)
200
+ params_proc&.call(params, parents, context)
201
201
  return [] if blank_query?(params)
202
202
  opts = load_options(parents, query)
203
203
  opts[:sideload] = self
@@ -426,13 +426,17 @@ module Graphiti
426
426
  return false if flag.blank?
427
427
 
428
428
  case flag.class.name
429
- when "Symbol","String"
429
+ when "Symbol", "String"
430
430
  resource.send(flag)
431
431
  when "Proc"
432
- self.resource.instance_exec(&flag)
432
+ resource.instance_exec(&flag)
433
433
  else
434
434
  !!flag
435
435
  end
436
436
  end
437
+
438
+ def context
439
+ Graphiti.context[:object]
440
+ end
437
441
  end
438
442
  end
@@ -1,6 +1,6 @@
1
1
  class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
2
2
  def initialize(name, opts)
3
- opts = { always_include_resource_ids: false }.merge(opts)
3
+ opts = {always_include_resource_ids: false}.merge(opts)
4
4
  super(name, opts)
5
5
  end
6
6
 
@@ -15,10 +15,10 @@ module Graphiti
15
15
  # }
16
16
  class Payload
17
17
  def initialize(resource, query, scope, data)
18
- @resource = resource
19
- @query = query
20
- @scope = scope
21
- @data = data
18
+ @resource = resource
19
+ @query = query
20
+ @scope = scope
21
+ @data = data
22
22
  end
23
23
 
24
24
  # Generate the payload for +{ meta: { stats: { ... } } }+
@@ -93,14 +93,14 @@ module Graphiti
93
93
  read: Dry::Types["coercible.string"],
94
94
  write: Dry::Types["coercible.integer"],
95
95
  kind: "scalar",
96
- description: "Base Type. Query/persist as integer, render as string.",
96
+ description: "Base Type. Query/persist as integer, render as string."
97
97
  },
98
98
  uuid: {
99
99
  params: Dry::Types["coercible.string"],
100
100
  read: Dry::Types["coercible.string"],
101
101
  write: Dry::Types["coercible.string"],
102
102
  kind: "scalar",
103
- description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.",
103
+ description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive."
104
104
  },
105
105
  string_enum: {
106
106
  canonical_name: :enum,
@@ -108,7 +108,7 @@ module Graphiti
108
108
  read: Dry::Types["coercible.string"],
109
109
  write: Dry::Types["coercible.string"],
110
110
  kind: "scalar",
111
- description: "String enum type. Like a normal string, but only eq/!eq and case-sensitive. Limited to only the allowed values.",
111
+ description: "String enum type. Like a normal string, but only eq/!eq and case-sensitive. Limited to only the allowed values."
112
112
  },
113
113
  integer_enum: {
114
114
  canonical_name: :enum,
@@ -116,71 +116,71 @@ module Graphiti
116
116
  read: Dry::Types["coercible.integer"],
117
117
  write: Dry::Types["coercible.integer"],
118
118
  kind: "scalar",
119
- description: "Integer enum type. Like a normal integer, but only eq/!eq filters. Limited to only the allowed values.",
119
+ description: "Integer enum type. Like a normal integer, but only eq/!eq filters. Limited to only the allowed values."
120
120
  },
121
121
  string: {
122
122
  params: Dry::Types["coercible.string"],
123
123
  read: Dry::Types["coercible.string"],
124
124
  write: Dry::Types["coercible.string"],
125
125
  kind: "scalar",
126
- description: "Base Type.",
126
+ description: "Base Type."
127
127
  },
128
128
  integer: {
129
129
  params: PresentInteger,
130
130
  read: Integer,
131
131
  write: Integer,
132
132
  kind: "scalar",
133
- description: "Base Type.",
133
+ description: "Base Type."
134
134
  },
135
135
  big_decimal: {
136
136
  params: ParamDecimal,
137
137
  read: Dry::Types["json.decimal"],
138
138
  write: Dry::Types["json.decimal"],
139
139
  kind: "scalar",
140
- description: "Base Type.",
140
+ description: "Base Type."
141
141
  },
142
142
  float: {
143
143
  params: Dry::Types["coercible.float"],
144
144
  read: Float,
145
145
  write: Float,
146
146
  kind: "scalar",
147
- description: "Base Type.",
147
+ description: "Base Type."
148
148
  },
149
149
  boolean: {
150
150
  params: PresentBool,
151
151
  read: Bool,
152
152
  write: Bool,
153
153
  kind: "scalar",
154
- description: "Base Type.",
154
+ description: "Base Type."
155
155
  },
156
156
  date: {
157
157
  params: PresentDate,
158
158
  read: Date,
159
159
  write: Date,
160
160
  kind: "scalar",
161
- description: "Base Type.",
161
+ description: "Base Type."
162
162
  },
163
163
  datetime: {
164
164
  params: PresentParamsDateTime,
165
165
  read: ReadDateTime,
166
166
  write: WriteDateTime,
167
167
  kind: "scalar",
168
- description: "Base Type.",
168
+ description: "Base Type."
169
169
  },
170
170
  hash: {
171
171
  params: PresentParamsHash,
172
172
  read: Dry::Types["strict.hash"],
173
173
  write: Dry::Types["strict.hash"],
174
174
  kind: "record",
175
- description: "Base Type.",
175
+ description: "Base Type."
176
176
  },
177
177
  array: {
178
178
  params: Dry::Types["strict.array"],
179
179
  read: Dry::Types["strict.array"],
180
180
  write: Dry::Types["strict.array"],
181
181
  kind: "array",
182
- description: "Base Type.",
183
- },
182
+ description: "Base Type."
183
+ }
184
184
  }
185
185
 
186
186
  hash.each_pair do |k, v|
@@ -198,7 +198,7 @@ module Graphiti
198
198
  test: Dry::Types["strict.array"].of(map[:test]),
199
199
  write: Dry::Types["strict.array"].of(map[:write]),
200
200
  kind: "array",
201
- description: "Base Type.",
201
+ description: "Base Type."
202
202
  }
203
203
  end
204
204
  hash.merge!(arrays)
@@ -63,7 +63,7 @@ module Graphiti
63
63
  params[:filter] = @sideload.base_filter([@model])
64
64
  end
65
65
 
66
- @sideload.params_proc&.call(params, [@model])
66
+ @sideload.params_proc&.call(params, [@model], context)
67
67
  end
68
68
  end
69
69
 
@@ -75,6 +75,10 @@ module Graphiti
75
75
  end
76
76
  path
77
77
  end
78
+
79
+ def context
80
+ Graphiti.context[:object]
81
+ end
78
82
  end
79
83
  end
80
84
  end
@@ -8,12 +8,12 @@ class Graphiti::Util::Persistence
8
8
  # @param [Model] caller_model The persisted parent object in the request graph
9
9
  # @param [Symbol] foreign_key Attribute assigned by parent object in graph
10
10
  def initialize(resource, meta, attributes, relationships, caller_model, foreign_key = nil)
11
- @resource = resource
12
- @meta = meta
13
- @attributes = attributes
11
+ @resource = resource
12
+ @meta = meta
13
+ @attributes = attributes
14
14
  @relationships = relationships
15
- @caller_model = caller_model
16
- @foreign_key = foreign_key
15
+ @caller_model = caller_model
16
+ @foreign_key = foreign_key
17
17
 
18
18
  # Find the correct child resource for a given jsonapi type
19
19
  if (meta_type = @meta[:type].try(:to_sym))
@@ -177,9 +177,15 @@ class Graphiti::Util::Persistence
177
177
  def process_belongs_to(relationships)
178
178
  [].tap do |processed|
179
179
  iterate(only: [:polymorphic_belongs_to, :belongs_to]) do |x|
180
- x[:object] = x[:resource]
181
- .persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
182
- processed << x
180
+ begin
181
+ id = x.dig(:attributes, :id)
182
+ x[:object] = x[:resource]
183
+ .persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
184
+ processed << x
185
+ rescue Graphiti::Errors::RecordNotFound
186
+ path = "relationships/#{x.dig(:meta, :jsonapi_type)}"
187
+ raise Graphiti::Errors::RecordNotFound.new(x[:sideload].name, id, path)
188
+ end
183
189
  end
184
190
  end
185
191
  end
@@ -202,7 +208,7 @@ class Graphiti::Util::Persistence
202
208
  def iterate(only: [], except: [])
203
209
  opts = {
204
210
  resource: @resource,
205
- relationships: @relationships,
211
+ relationships: @relationships
206
212
  }.merge(only: only, except: except)
207
213
 
208
214
  Graphiti::Util::RelationshipPayload.iterate(opts) do |x|
@@ -216,7 +222,7 @@ class Graphiti::Util::Persistence
216
222
  temp_id: @meta[:temp_id],
217
223
  caller_model: @caller_model,
218
224
  attributes: @attributes,
219
- relationships: @relationships,
225
+ relationships: @relationships
220
226
  }
221
227
  end
222
228
 
@@ -14,9 +14,9 @@ module Graphiti
14
14
 
15
15
  def initialize(resource, payload, only: [], except: [])
16
16
  @resource = resource
17
- @payload = payload
18
- @only = only
19
- @except = except
17
+ @payload = payload
18
+ @only = only
19
+ @except = except
20
20
  end
21
21
 
22
22
  def iterate
@@ -67,7 +67,7 @@ module Graphiti
67
67
  foreign_key: sideload.foreign_key,
68
68
  attributes: relationship_payload[:attributes],
69
69
  meta: relationship_payload[:meta],
70
- relationships: relationship_payload[:relationships],
70
+ relationships: relationship_payload[:relationships]
71
71
  }
72
72
  end
73
73
  end
@@ -53,7 +53,7 @@ module Graphiti
53
53
  def add(attribute, code, message: nil)
54
54
  message ||= "is #{code.to_s.humanize.downcase}"
55
55
 
56
- details[attribute.to_sym] << {error: code}
56
+ details[attribute.to_sym] << {error: code}
57
57
  messages[attribute.to_sym] << message
58
58
  end
59
59
 
@@ -65,7 +65,7 @@ module Graphiti
65
65
  Thread.current[:_graphiti_hooks] = {
66
66
  after_graph_persist: [],
67
67
  before_commit: [],
68
- after_commit: [],
68
+ after_commit: []
69
69
  }
70
70
  end
71
71
 
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.2.16"
2
+ VERSION = "1.2.17"
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.2.16
4
+ version: 1.2.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-31 00:00:00.000000000 Z
11
+ date: 2020-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -241,8 +241,12 @@ files:
241
241
  - exe/graphiti
242
242
  - gemfiles/.bundle/config
243
243
  - gemfiles/rails_4.gemfile
244
- - gemfiles/rails_5.gemfile
245
- - gemfiles/rails_5_graphiti_rails.gemfile
244
+ - gemfiles/rails_5_0.gemfile
245
+ - gemfiles/rails_5_0_graphiti_rails.gemfile
246
+ - gemfiles/rails_5_1.gemfile
247
+ - gemfiles/rails_5_1_graphiti_rails.gemfile
248
+ - gemfiles/rails_5_2.gemfile
249
+ - gemfiles/rails_5_2_graphiti_rails.gemfile
246
250
  - gemfiles/rails_6.gemfile
247
251
  - gemfiles/rails_6_graphiti_rails.gemfile
248
252
  - graphiti.gemspec