graphiti 1.2.16 → 1.2.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +43 -14
  3. data/Appraisals +37 -5
  4. data/CHANGELOG.md +1 -1
  5. data/Gemfile +2 -0
  6. data/Guardfile +5 -5
  7. data/deprecated_generators/graphiti/resource_generator.rb +1 -1
  8. data/gemfiles/rails_5_0.gemfile +18 -0
  9. data/gemfiles/rails_5_0_graphiti_rails.gemfile +20 -0
  10. data/gemfiles/rails_5_1.gemfile +18 -0
  11. data/gemfiles/rails_5_1_graphiti_rails.gemfile +20 -0
  12. data/gemfiles/{rails_5.gemfile → rails_5_2.gemfile} +0 -0
  13. data/gemfiles/{rails_5_graphiti_rails.gemfile → rails_5_2_graphiti_rails.gemfile} +0 -0
  14. data/gemfiles/rails_6.gemfile +1 -1
  15. data/gemfiles/rails_6_graphiti_rails.gemfile +1 -1
  16. data/graphiti.gemspec +11 -12
  17. data/lib/graphiti.rb +3 -3
  18. data/lib/graphiti/adapters/abstract.rb +3 -3
  19. data/lib/graphiti/adapters/active_record.rb +65 -36
  20. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +2 -1
  21. data/lib/graphiti/configuration.rb +1 -1
  22. data/lib/graphiti/debugger.rb +12 -8
  23. data/lib/graphiti/delegates/pagination.rb +8 -4
  24. data/lib/graphiti/deserializer.rb +3 -3
  25. data/lib/graphiti/errors.rb +24 -4
  26. data/lib/graphiti/extensions/extra_attribute.rb +1 -1
  27. data/lib/graphiti/query.rb +32 -16
  28. data/lib/graphiti/railtie.rb +1 -1
  29. data/lib/graphiti/request_validator.rb +4 -4
  30. data/lib/graphiti/request_validators/update_validator.rb +1 -2
  31. data/lib/graphiti/request_validators/validator.rb +2 -2
  32. data/lib/graphiti/resource.rb +10 -0
  33. data/lib/graphiti/resource/configuration.rb +10 -3
  34. data/lib/graphiti/resource/dsl.rb +10 -4
  35. data/lib/graphiti/resource/interface.rb +2 -2
  36. data/lib/graphiti/resource/links.rb +3 -3
  37. data/lib/graphiti/resource/persistence.rb +2 -1
  38. data/lib/graphiti/resource/polymorphism.rb +2 -1
  39. data/lib/graphiti/resource/remote.rb +1 -1
  40. data/lib/graphiti/runner.rb +4 -3
  41. data/lib/graphiti/schema.rb +6 -6
  42. data/lib/graphiti/scope.rb +5 -5
  43. data/lib/graphiti/scoping/base.rb +3 -3
  44. data/lib/graphiti/scoping/filter.rb +17 -7
  45. data/lib/graphiti/scoping/sort.rb +1 -1
  46. data/lib/graphiti/serializer.rb +7 -0
  47. data/lib/graphiti/sideload.rb +30 -22
  48. data/lib/graphiti/sideload/belongs_to.rb +1 -1
  49. data/lib/graphiti/sideload/has_many.rb +19 -1
  50. data/lib/graphiti/sideload/many_to_many.rb +6 -2
  51. data/lib/graphiti/stats/payload.rb +4 -4
  52. data/lib/graphiti/types.rb +15 -15
  53. data/lib/graphiti/util/link.rb +6 -2
  54. data/lib/graphiti/util/persistence.rb +16 -10
  55. data/lib/graphiti/util/relationship_payload.rb +4 -4
  56. data/lib/graphiti/util/simple_errors.rb +1 -1
  57. data/lib/graphiti/util/transaction_hooks_recorder.rb +1 -1
  58. data/lib/graphiti/version.rb +1 -1
  59. metadata +18 -22
@@ -11,7 +11,7 @@ module Graphiti
11
11
 
12
12
  # @api private
13
13
  def _all(params, opts, base_scope)
14
- runner = Runner.new(self, params, opts.delete(:query))
14
+ runner = Runner.new(self, params, opts.delete(:query), :all)
15
15
  opts[:params] = params
16
16
  runner.proxy(base_scope, opts)
17
17
  end
@@ -27,7 +27,7 @@ module Graphiti
27
27
  params[:filter] ||= {}
28
28
  params[:filter][:id] = id if id
29
29
 
30
- runner = Runner.new(self, params)
30
+ runner = Runner.new(self, params, nil, :find)
31
31
  runner.proxy base_scope,
32
32
  single: true,
33
33
  raise_on_missing: true,
@@ -39,7 +39,7 @@ module Graphiti
39
39
  path: path,
40
40
  full_path: full_path_for(path),
41
41
  url: url_for(path),
42
- actions: DEFAULT_ACTIONS.dup,
42
+ actions: DEFAULT_ACTIONS.dup
43
43
  }
44
44
  end
45
45
 
@@ -49,7 +49,7 @@ module Graphiti
49
49
  path: path,
50
50
  full_path: full_path_for(path),
51
51
  url: url_for(path),
52
- actions: actions,
52
+ actions: actions
53
53
  }
54
54
  end
55
55
 
@@ -60,7 +60,7 @@ module Graphiti
60
60
  path: path,
61
61
  full_path: full_path_for(path),
62
62
  url: url_for(path),
63
- actions: actions,
63
+ actions: actions
64
64
  }]
65
65
  end
66
66
 
@@ -89,7 +89,8 @@ module Graphiti
89
89
 
90
90
  def update(update_params, meta = nil)
91
91
  model_instance = nil
92
- id = update_params.delete(:id)
92
+ id = update_params[:id]
93
+ update_params = update_params.except(:id)
93
94
 
94
95
  run_callbacks :persistence, :update, update_params, meta do
95
96
  run_callbacks :attributes, :update, update_params, meta do |params|
@@ -67,7 +67,8 @@ module Graphiti
67
67
  end
68
68
 
69
69
  def resource_for_model(model)
70
- resource = children.find { |c| model.is_a?(c.model) }
70
+ resource = children.find { |c| model.class == c.model } ||
71
+ children.find { |c| model.is_a?(c.model) }
71
72
  if resource.nil?
72
73
  raise Errors::PolymorphicResourceChildNotFound.new(self, model: model)
73
74
  else
@@ -19,7 +19,7 @@ module Graphiti
19
19
  end
20
20
 
21
21
  def save(model, meta)
22
- if meta[:attributes] == {} && meta[:method] == :update
22
+ if meta[:attributes].except(:id) == {} && meta[:method] == :update
23
23
  model
24
24
  else
25
25
  raise Errors::RemoteWrite.new(self.class)
@@ -3,10 +3,11 @@ module Graphiti
3
3
  attr_reader :params
4
4
  attr_reader :deserialized_payload
5
5
 
6
- def initialize(resource_class, params, query = nil)
6
+ def initialize(resource_class, params, query = nil, action = nil)
7
7
  @resource_class = resource_class
8
8
  @params = params
9
9
  @query = query
10
+ @action = action
10
11
 
11
12
  validator = RequestValidator.new(jsonapi_resource, params)
12
13
 
@@ -30,7 +31,7 @@ module Graphiti
30
31
  end
31
32
 
32
33
  def query
33
- @query ||= Query.new(jsonapi_resource, params)
34
+ @query ||= Query.new(jsonapi_resource, params, nil, nil, [], @action)
34
35
  end
35
36
 
36
37
  def query_hash
@@ -50,7 +51,7 @@ module Graphiti
50
51
  def jsonapi_render_options
51
52
  options = {}
52
53
  options.merge!(default_jsonapi_render_options)
53
- options[:meta] ||= {}
54
+ options[:meta] ||= {}
54
55
  options[:expose] ||= {}
55
56
  options[:expose][:context] = jsonapi_context
56
57
  options
@@ -33,7 +33,7 @@ module Graphiti
33
33
  {
34
34
  resources: generate_resources,
35
35
  endpoints: generate_endpoints,
36
- types: generate_types,
36
+ types: generate_types
37
37
  }
38
38
  end
39
39
 
@@ -94,7 +94,7 @@ module Graphiti
94
94
  extra_attributes: extra_attributes(r),
95
95
  sorts: sorts(r),
96
96
  filters: filters(r),
97
- relationships: relationships(r),
97
+ relationships: relationships(r)
98
98
  }
99
99
 
100
100
  if r.default_sort
@@ -121,7 +121,7 @@ module Graphiti
121
121
  name: r.name,
122
122
  description: r.description,
123
123
  remote: r.remote_url,
124
- relationships: relationships(r),
124
+ relationships: relationships(r)
125
125
  }
126
126
  }
127
127
 
@@ -136,7 +136,7 @@ module Graphiti
136
136
  type: config[:type].to_s,
137
137
  readable: flag(config[:readable]),
138
138
  writable: flag(config[:writable]),
139
- description: resource.attribute_description(name),
139
+ description: resource.attribute_description(name)
140
140
  }
141
141
  end
142
142
  end
@@ -149,7 +149,7 @@ module Graphiti
149
149
  attrs[name] = {
150
150
  type: config[:type].to_s,
151
151
  readable: flag(config[:readable]),
152
- description: resource.attribute_description(name),
152
+ description: resource.attribute_description(name)
153
153
  }
154
154
  end
155
155
  end
@@ -186,7 +186,7 @@ module Graphiti
186
186
 
187
187
  config = {
188
188
  type: filter[:type].to_s,
189
- operators: filter[:operators].keys.map(&:to_s),
189
+ operators: filter[:operators].keys.map(&:to_s)
190
190
  }
191
191
 
192
192
  config[:single] = true if filter[:single]
@@ -3,10 +3,10 @@ module Graphiti
3
3
  attr_accessor :object, :unpaginated_object
4
4
  attr_reader :pagination
5
5
  def initialize(object, resource, query, opts = {})
6
- @object = object
7
- @resource = resource
8
- @query = query
9
- @opts = opts
6
+ @object = object
7
+ @resource = resource
8
+ @query = query
9
+ @opts = opts
10
10
 
11
11
  @object = @resource.around_scoping(@object, @query.hash) { |scope|
12
12
  apply_scoping(scope, opts)
@@ -75,7 +75,7 @@ module Graphiti
75
75
  resource: @resource,
76
76
  params: @opts[:params],
77
77
  sideload: @opts[:sideload],
78
- parent: @opts[:parent],
78
+ parent: @opts[:parent]
79
79
  # Set once data is resolved within block
80
80
  # results: ...
81
81
  }
@@ -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
@@ -25,6 +25,7 @@ module Graphiti
25
25
  def as_jsonapi(*)
26
26
  super.tap do |hash|
27
27
  strip_relationships!(hash) if strip_relationships?
28
+ add_links!(hash)
28
29
  end
29
30
  end
30
31
 
@@ -49,6 +50,12 @@ module Graphiti
49
50
 
50
51
  private
51
52
 
53
+ def add_links!(hash)
54
+ return unless @resource.respond_to?(:links?)
55
+
56
+ hash[:links] = @resource.links(@object) if @resource.links?
57
+ end
58
+
52
59
  def strip_relationships!(hash)
53
60
  hash[:relationships]&.select! do |name, payload|
54
61
  payload.key?(:data)
@@ -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
@@ -132,6 +132,10 @@ module Graphiti
132
132
  end
133
133
  end
134
134
 
135
+ def link_filter(parents)
136
+ base_filter(parents)
137
+ end
138
+
135
139
  # The parent resource is a remote,
136
140
  # AND the sideload is a remote to the same endpoint
137
141
  def shared_remote?
@@ -197,7 +201,7 @@ module Graphiti
197
201
 
198
202
  with_error_handling Errors::SideloadParamsError do
199
203
  params = load_params(parents, query)
200
- params_proc&.call(params, parents)
204
+ params_proc&.call(params, parents, context)
201
205
  return [] if blank_query?(params)
202
206
  opts = load_options(parents, query)
203
207
  opts[:sideload] = self
@@ -426,13 +430,17 @@ module Graphiti
426
430
  return false if flag.blank?
427
431
 
428
432
  case flag.class.name
429
- when "Symbol","String"
433
+ when "Symbol", "String"
430
434
  resource.send(flag)
431
435
  when "Proc"
432
- self.resource.instance_exec(&flag)
436
+ resource.instance_exec(&flag)
433
437
  else
434
438
  !!flag
435
439
  end
436
440
  end
441
+
442
+ def context
443
+ Graphiti.context[:object]
444
+ end
437
445
  end
438
446
  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
 
@@ -1,8 +1,18 @@
1
1
  class Graphiti::Sideload::HasMany < Graphiti::Sideload
2
+ def initialize(name, opts)
3
+ @inverse_filter = opts[:inverse_filter]
4
+
5
+ super(name, opts)
6
+ end
7
+
2
8
  def type
3
9
  :has_many
4
10
  end
5
11
 
12
+ def inverse_filter
13
+ @inverse_filter || foreign_key
14
+ end
15
+
6
16
  def load_params(parents, query)
7
17
  query.hash.tap do |hash|
8
18
  hash[:filter] ||= {}
@@ -11,11 +21,19 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
11
21
  end
12
22
 
13
23
  def base_filter(parents)
14
- {foreign_key => ids_for_parents(parents).join(",")}
24
+ {foreign_key => parent_filter(parents)}
25
+ end
26
+
27
+ def link_filter(parents)
28
+ {inverse_filter => parent_filter(parents)}
15
29
  end
16
30
 
17
31
  private
18
32
 
33
+ def parent_filter(parents)
34
+ ids_for_parents(parents).join(",")
35
+ end
36
+
19
37
  def child_map(children)
20
38
  children.group_by(&foreign_key)
21
39
  end
@@ -11,8 +11,12 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
11
11
  foreign_key.values.first
12
12
  end
13
13
 
14
+ def inverse_filter
15
+ @inverse_filter || true_foreign_key
16
+ end
17
+
14
18
  def base_filter(parents)
15
- {true_foreign_key => ids_for_parents(parents).join(",")}
19
+ {true_foreign_key => parent_filter(parents)}
16
20
  end
17
21
 
18
22
  def infer_foreign_key
@@ -32,7 +36,7 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
32
36
  self_ref = self
33
37
  fk_type = parent_resource_class.attributes[:id][:type]
34
38
  fk_type = :hash if polymorphic?
35
- resource_class.filter true_foreign_key, fk_type do
39
+ resource_class.filter inverse_filter, fk_type do
36
40
  eq do |scope, value|
37
41
  self_ref.belongs_to_many_filter(scope, value)
38
42
  end