graphiti 1.2.15 → 1.2.20

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 +4 -4
  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 +3 -2
  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
@@ -36,7 +36,8 @@ class Graphiti::Adapters::ActiveRecord::ManyToManySideload < Graphiti::Sideload:
36
36
 
37
37
  def filter_for(scope, value, type = nil)
38
38
  scope
39
- .includes(through_relationship_name)
39
+ .preload(through_relationship_name)
40
+ .joins(through_relationship_name)
40
41
  .where(belongs_to_many_clause(value, type))
41
42
  end
42
43
 
@@ -43,7 +43,7 @@ module Graphiti
43
43
  end
44
44
 
45
45
  if (logger = ::Rails.logger)
46
- self.debug = logger.level.zero? && self.debug
46
+ self.debug = logger.level.zero? && debug
47
47
  Graphiti.logger = logger
48
48
  end
49
49
  end
@@ -59,10 +59,10 @@ module Graphiti
59
59
  add_chunk(payload[:resource], payload[:parent]) do |logs, json|
60
60
  logs << [" \\_ #{sideload.name}", :yellow, true]
61
61
  json[:name] = sideload.name
62
- if sideload.class.scope_proc
63
- query = "#{payload[:resource].class.name}: Manual sideload via .scope"
62
+ query = if sideload.class.scope_proc
63
+ "#{payload[:resource].class.name}: Manual sideload via .scope"
64
64
  else
65
- query = "#{payload[:resource].class.name}.all(#{params.inspect})"
65
+ "#{payload[:resource].class.name}.all(#{params.inspect})"
66
66
  end
67
67
  logs << [" #{query}", :cyan, true]
68
68
  json[:query] = query
@@ -148,7 +148,7 @@ module Graphiti
148
148
  parent: parent,
149
149
  logs: logs,
150
150
  json: json,
151
- children: [],
151
+ children: []
152
152
  }
153
153
  end
154
154
 
@@ -15,22 +15,26 @@ module Graphiti
15
15
  links[:last] = pagination_link(last_page)
16
16
  links[:prev] = pagination_link(current_page - 1) unless current_page == 1
17
17
  links[:next] = pagination_link(current_page + 1) unless current_page == last_page
18
- end.select {|k, v| !v.nil? }
18
+ end.select { |k, v| !v.nil? }
19
19
  end
20
20
 
21
21
  private
22
22
 
23
+ def pagination_params
24
+ @pagination_params ||= @proxy.query.params.reject { |key, _| [:action, :controller, :format].include?(key) }
25
+ end
26
+
23
27
  def pagination_link(page)
24
28
  return nil unless @proxy.resource.endpoint
25
29
 
26
30
  uri = URI(@proxy.resource.endpoint[:url].to_s)
27
31
 
28
32
  # Overwrite the pagination query params with the desired page
29
- uri.query = @proxy.query.hash.merge({
33
+ uri.query = pagination_params.merge({
30
34
  page: {
31
35
  number: page,
32
- size: page_size,
33
- },
36
+ size: page_size
37
+ }
34
38
  }).to_query
35
39
  uri.to_s
36
40
  end
@@ -87,7 +87,7 @@ class Graphiti::Deserializer
87
87
  type: data[:type],
88
88
  temp_id: data[:'temp-id'],
89
89
  method: action,
90
- payload_path: ["data"],
90
+ payload_path: ["data"]
91
91
  }
92
92
  end
93
93
 
@@ -185,10 +185,10 @@ class Graphiti::Deserializer
185
185
  jsonapi_type: datum[:type],
186
186
  temp_id: temp_id,
187
187
  method: method,
188
- payload_path: ["included", included_idx],
188
+ payload_path: ["included", included_idx]
189
189
  },
190
190
  attributes: attributes,
191
- relationships: relationships,
191
+ relationships: relationships
192
192
  }
193
193
  end
194
194
 
@@ -143,8 +143,13 @@ module Graphiti
143
143
  def message
144
144
  allow = @filter.values[0][:allow]
145
145
  deny = @filter.values[0][:deny]
146
+ value_string = if @value == "(empty)"
147
+ "empty value"
148
+ else
149
+ "value #{@value.inspect}"
150
+ end
146
151
  msg = <<-MSG
147
- #{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid value #{@value.inspect}.
152
+ #{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid #{value_string}.
148
153
  MSG
149
154
  msg << "\nAllowlist: #{allow.inspect}" if allow
150
155
  msg << "\nDenylist: #{deny.inspect}" if deny
@@ -189,7 +194,7 @@ module Graphiti
189
194
 
190
195
  Make sure the endpoint "#{@sideload.resource.endpoint[:full_path]}" exists with action #{@action.inspect}, or customize the endpoint for #{@sideload.resource.class.name}.
191
196
 
192
- If you do not wish to generate a link, pass link: false or set self.relationship_links_by_default = false.
197
+ If you do not wish to generate a link, pass link: false or set self.autolink = false.
193
198
  MSG
194
199
  end
195
200
  end
@@ -316,14 +321,14 @@ module Graphiti
316
321
  sortable: "sort on",
317
322
  filterable: "filter on",
318
323
  readable: "read",
319
- writable: "write",
324
+ writable: "write"
320
325
  }[@flag]
321
326
  else
322
327
  {
323
328
  sortable: "add sort",
324
329
  filterable: "add filter",
325
330
  readable: "read",
326
- writable: "write",
331
+ writable: "write"
327
332
  }[@flag]
328
333
  end
329
334
  end
@@ -722,6 +727,21 @@ module Graphiti
722
727
  end
723
728
 
724
729
  class RecordNotFound < Base
730
+ def initialize(resource = nil, id = nil, path = nil)
731
+ @resource = resource
732
+ @id = id
733
+ @path = path
734
+ end
735
+
736
+ def message
737
+ if !@resource.nil? && !@id.nil?
738
+ "The referenced resource '#{@resource}' with id '#{@id}' could not be found.".tap do |msg|
739
+ msg << " Referenced at '#{@path}'" unless @path.nil?
740
+ end
741
+ else
742
+ "Specified Record Not Found"
743
+ end
744
+ end
725
745
  end
726
746
 
727
747
  class RequiredFilter < Base
@@ -43,7 +43,7 @@ module Graphiti
43
43
  def extra_attribute(name, options = {}, &blk)
44
44
  allow_field = proc {
45
45
  if options[:if]
46
- next false unless instance_eval(&options[:if])
46
+ next false unless instance_exec(&options[:if])
47
47
  end
48
48
 
49
49
  @extra_fields &&
@@ -1,8 +1,8 @@
1
1
  module Graphiti
2
2
  class Query
3
- attr_reader :resource, :association_name, :params
3
+ attr_reader :resource, :association_name, :params, :action
4
4
 
5
- def initialize(resource, params, association_name = nil, nested_include = nil, parents = [])
5
+ def initialize(resource, params, association_name = nil, nested_include = nil, parents = [], action = nil)
6
6
  @resource = resource
7
7
  @association_name = association_name
8
8
  @params = params
@@ -11,6 +11,7 @@ module Graphiti
11
11
  @params = @params.deep_symbolize_keys
12
12
  @include_param = nested_include || @params[:include]
13
13
  @parents = parents
14
+ @action = parse_action(action)
14
15
  end
15
16
 
16
17
  def association?
@@ -44,16 +45,19 @@ module Graphiti
44
45
 
45
46
  def hash
46
47
  @hash ||= {}.tap do |h|
47
- h[:filter] = filters unless filters.empty?
48
- h[:sort] = sorts unless sorts.empty?
49
- h[:page] = pagination unless pagination.empty?
50
- unless association?
51
- h[:fields] = fields unless fields.empty?
52
- h[:extra_fields] = extra_fields unless extra_fields.empty?
48
+ h[:filter] = filters
49
+ h[:sort] = sorts
50
+ h[:page] = pagination
51
+ if association?
52
+ resource_type = @resource.class.type
53
+ h[:extra_fields] = {resource_type => extra_fields[resource_type]} if extra_fields.key?(resource_type)
54
+ else
55
+ h[:fields] = fields
56
+ h[:extra_fields] = extra_fields
53
57
  end
54
- h[:stats] = stats unless stats.empty?
55
- h[:include] = sideload_hash unless sideload_hash.empty?
56
- end
58
+ h[:stats] = stats
59
+ h[:include] = sideload_hash
60
+ end.reject { |_, value| value.empty? }
57
61
  end
58
62
 
59
63
  def zero_results?
@@ -92,7 +96,7 @@ module Graphiti
92
96
  sl_resource = resource_for_sideload(sideload)
93
97
  query_parents = parents + [self]
94
98
  sub_hash = sub_hash[:include] if sub_hash.key?(:include)
95
- hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents)
99
+ hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents, :all)
96
100
  else
97
101
  handle_missing_sideload(key)
98
102
  end
@@ -232,7 +236,7 @@ module Graphiti
232
236
  return true if @resource.remote?
233
237
 
234
238
  if (att = @resource.get_attr(name, flag, request: true))
235
- return att
239
+ att
236
240
  else
237
241
  not_associated_name = !@resource.class.association_names.include?(name)
238
242
  not_associated_type = !@resource.class.association_types.include?(name)
@@ -262,8 +266,8 @@ module Graphiti
262
266
  def parse_fieldset(fieldset)
263
267
  {}.tap do |hash|
264
268
  fieldset.each_pair do |type, fields|
265
- type = type.to_sym
266
- fields = fields.split(",") unless fields.is_a?(Array)
269
+ type = type.to_sym
270
+ fields = fields.split(",") unless fields.is_a?(Array)
267
271
  hash[type] = fields.map(&:to_sym)
268
272
  end
269
273
  end
@@ -282,7 +286,7 @@ module Graphiti
282
286
 
283
287
  def sort_hash(attr)
284
288
  value = attr[0] == "-" ? :desc : :asc
285
- key = attr.sub("-", "").to_sym
289
+ key = attr.sub("-", "").to_sym
286
290
 
287
291
  {key => value}
288
292
  end
@@ -312,5 +316,17 @@ module Graphiti
312
316
  end
313
317
  end
314
318
  end
319
+
320
+ def parse_action(action)
321
+ action ||= @params.fetch(:action, Graphiti.context[:namespace]).try(:to_sym)
322
+ case action
323
+ when :index
324
+ :all
325
+ when :show
326
+ :find
327
+ else
328
+ action
329
+ end
330
+ end
315
331
  end
316
332
  end
@@ -6,7 +6,7 @@ module Graphiti
6
6
  end
7
7
 
8
8
  generators do
9
- Dir[File.expand_path("../../deprecated_generators/**/*.rb", __dir__)].each do |f|
9
+ Dir[File.expand_path("../../deprecated_generators/**/*.rb", __dir__)].sort.each do |f|
10
10
  require f
11
11
  end
12
12
  end
@@ -1,10 +1,10 @@
1
1
  module Graphiti
2
2
  class RequestValidator
3
3
  delegate :validate,
4
- :validate!,
5
- :errors,
6
- :deserialized_payload,
7
- to: :@validator
4
+ :validate!,
5
+ :errors,
6
+ :deserialized_payload,
7
+ to: :@validator
8
8
 
9
9
  def initialize(root_resource, raw_params)
10
10
  @validator = ValidatorFactory.create(root_resource, raw_params)
@@ -5,7 +5,7 @@ module Graphiti
5
5
  if required_payload? && payload_matches_endpoint?
6
6
  super
7
7
  else
8
- return false
8
+ false
9
9
  end
10
10
  end
11
11
 
@@ -36,7 +36,6 @@ module Graphiti
36
36
  attribute_mismatch([:data, :id])
37
37
  end
38
38
 
39
-
40
39
  meta_type = @raw_params.dig(:data, :type)
41
40
 
42
41
  # NOTE: calling #to_s and comparing 2 strings is slower than
@@ -25,7 +25,7 @@ module Graphiti
25
25
 
26
26
  def validate!
27
27
  unless validate
28
- raise @error_class || Graphiti::Errors::InvalidRequest, self.errors
28
+ raise @error_class || Graphiti::Errors::InvalidRequest, errors
29
29
  end
30
30
 
31
31
  true
@@ -47,7 +47,7 @@ module Graphiti
47
47
  def process_relationships(resource, relationships, payload_path)
48
48
  opts = {
49
49
  resource: resource,
50
- relationships: relationships,
50
+ relationships: relationships
51
51
  }
52
52
 
53
53
  Graphiti::Util::RelationshipPayload.iterate(opts) do |x|
@@ -148,5 +148,15 @@ module Graphiti
148
148
  end
149
149
  response
150
150
  end
151
+
152
+ def links?
153
+ self.class.links.any?
154
+ end
155
+
156
+ def links(model)
157
+ self.class.links.each_with_object({}) do |(name, blk), memo|
158
+ memo[name] = instance_exec(model, &blk)
159
+ end
160
+ end
151
161
  end
152
162
  end
@@ -22,7 +22,7 @@ module Graphiti
22
22
  end
23
23
 
24
24
  def type=(val)
25
- val = val && val.to_sym
25
+ val = val&.to_sym
26
26
  if (val = super)
27
27
  serializer.type(val)
28
28
  end
@@ -42,7 +42,7 @@ module Graphiti
42
42
  path: val,
43
43
  full_path: val,
44
44
  url: val,
45
- actions: [:index, :show],
45
+ actions: [:index, :show]
46
46
  }
47
47
  end
48
48
 
@@ -82,7 +82,8 @@ module Graphiti
82
82
  :attributes_schema_by_default,
83
83
  :relationships_readable_by_default,
84
84
  :relationships_writable_by_default,
85
- :filters_accept_nil_by_default
85
+ :filters_accept_nil_by_default,
86
+ :filters_deny_empty_by_default
86
87
 
87
88
  class << self
88
89
  prepend Overrides
@@ -104,6 +105,7 @@ module Graphiti
104
105
  default(klass, :relationships_readable_by_default, true)
105
106
  default(klass, :relationships_writable_by_default, true)
106
107
  default(klass, :filters_accept_nil_by_default, false)
108
+ default(klass, :filters_deny_empty_by_default, false)
107
109
 
108
110
  unless klass.config[:attributes][:id]
109
111
  klass.attribute :id, :integer_id
@@ -198,6 +200,7 @@ module Graphiti
198
200
  extra_attributes: {},
199
201
  sideloads: {},
200
202
  callbacks: {},
203
+ links: {}
201
204
  }
202
205
  end
203
206
 
@@ -236,6 +239,10 @@ module Graphiti
236
239
  def default_filters
237
240
  config[:default_filters]
238
241
  end
242
+
243
+ def links
244
+ config[:links]
245
+ end
239
246
  end
240
247
 
241
248
  def get_attr!(name, flag, options = {})
@@ -32,7 +32,8 @@ module Graphiti
32
32
  dependencies: opts[:dependent],
33
33
  required: required,
34
34
  operators: operators.to_hash,
35
- allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default)
35
+ allow_nil: opts.fetch(:allow_nil, filters_accept_nil_by_default),
36
+ deny_empty: opts.fetch(:deny_empty, filters_deny_empty_by_default)
36
37
  }
37
38
  elsif (type = args[0])
38
39
  attribute name, type, only: [:filterable], allow: opts[:allow]
@@ -55,7 +56,7 @@ module Graphiti
55
56
 
56
57
  if get_attr(name, :sortable, raise_error: :only_unsupported)
57
58
  config[:sorts][name] = {
58
- proc: blk,
59
+ proc: blk
59
60
  }.merge(opts.slice(:only))
60
61
  elsif (type = args[0])
61
62
  attribute name, type, only: [:sortable]
@@ -78,7 +79,7 @@ module Graphiti
78
79
  def default_filter(name = nil, &blk)
79
80
  name ||= :__default
80
81
  config[:default_filters][name.to_sym] = {
81
- filter: blk,
82
+ filter: blk
82
83
  }
83
84
  end
84
85
 
@@ -131,9 +132,10 @@ module Graphiti
131
132
  readable: true,
132
133
  writable: false,
133
134
  sortable: false,
134
- filterable: false,
135
+ filterable: false
135
136
  }
136
137
  options = defaults.merge(options)
138
+ attribute_option(options, :readable)
137
139
  config[:extra_attributes][name] = options
138
140
  apply_extra_attributes_to_serializer
139
141
  end
@@ -146,6 +148,10 @@ module Graphiti
146
148
  end
147
149
  end
148
150
 
151
+ def link(name, &blk)
152
+ config[:links][name.to_sym] = blk
153
+ end
154
+
149
155
  def all_attributes
150
156
  attributes.merge(extra_attributes)
151
157
  end