graphiti 1.2.16 → 1.2.17

Sign up to get free protection for your applications and to get access to all the features.
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