graphiti 1.0.rc.21 → 1.0.rc.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.standard.yml +14 -0
  4. data/.travis.yml +31 -2
  5. data/Appraisals +16 -10
  6. data/Gemfile +5 -5
  7. data/Guardfile +2 -2
  8. data/README.md +1 -1
  9. data/Rakefile +4 -4
  10. data/exe/graphiti +1 -1
  11. data/gemfiles/rails_4.gemfile +0 -1
  12. data/gemfiles/rails_5.gemfile +0 -1
  13. data/gemfiles/rails_6.gemfile +19 -0
  14. data/graphiti.gemspec +15 -14
  15. data/lib/generators/graphiti/api_test_generator.rb +16 -16
  16. data/lib/generators/graphiti/generator_mixin.rb +7 -7
  17. data/lib/generators/graphiti/install_generator.rb +19 -19
  18. data/lib/generators/graphiti/resource_generator.rb +19 -19
  19. data/lib/generators/graphiti/resource_test_generator.rb +10 -10
  20. data/lib/graphiti.rb +24 -26
  21. data/lib/graphiti/adapters/abstract.rb +25 -39
  22. data/lib/graphiti/adapters/active_record.rb +43 -49
  23. data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +33 -11
  24. data/lib/graphiti/adapters/graphiti_api.rb +16 -16
  25. data/lib/graphiti/adapters/null.rb +0 -12
  26. data/lib/graphiti/cli.rb +7 -7
  27. data/lib/graphiti/configuration.rb +14 -15
  28. data/lib/graphiti/context.rb +1 -1
  29. data/lib/graphiti/debugger.rb +22 -26
  30. data/lib/graphiti/delegates/pagination.rb +9 -9
  31. data/lib/graphiti/deserializer.rb +7 -9
  32. data/lib/graphiti/errors.rb +119 -119
  33. data/lib/graphiti/extensions/boolean_attribute.rb +1 -1
  34. data/lib/graphiti/extensions/extra_attribute.rb +1 -1
  35. data/lib/graphiti/extensions/temp_id.rb +1 -1
  36. data/lib/graphiti/filter_operators.rb +6 -0
  37. data/lib/graphiti/hash_renderer.rb +15 -15
  38. data/lib/graphiti/jsonapi_serializable_ext.rb +1 -1
  39. data/lib/graphiti/query.rb +35 -35
  40. data/lib/graphiti/railtie.rb +14 -10
  41. data/lib/graphiti/renderer.rb +2 -2
  42. data/lib/graphiti/resource.rb +4 -6
  43. data/lib/graphiti/resource/configuration.rb +9 -13
  44. data/lib/graphiti/resource/documentation.rb +4 -2
  45. data/lib/graphiti/resource/dsl.rb +21 -25
  46. data/lib/graphiti/resource/interface.rb +3 -3
  47. data/lib/graphiti/resource/links.rb +14 -14
  48. data/lib/graphiti/resource/persistence.rb +9 -9
  49. data/lib/graphiti/resource/polymorphism.rb +3 -3
  50. data/lib/graphiti/resource/remote.rb +3 -3
  51. data/lib/graphiti/resource/sideloading.rb +5 -5
  52. data/lib/graphiti/resource_proxy.rb +12 -12
  53. data/lib/graphiti/schema.rb +27 -26
  54. data/lib/graphiti/schema_diff.rb +10 -10
  55. data/lib/graphiti/scope.rb +12 -16
  56. data/lib/graphiti/scoping/base.rb +5 -5
  57. data/lib/graphiti/scoping/default_filter.rb +1 -1
  58. data/lib/graphiti/scoping/filter.rb +15 -24
  59. data/lib/graphiti/scoping/filterable.rb +5 -5
  60. data/lib/graphiti/scoping/paginate.rb +1 -1
  61. data/lib/graphiti/scoping/sort.rb +7 -7
  62. data/lib/graphiti/serializer.rb +8 -4
  63. data/lib/graphiti/sideload.rb +23 -39
  64. data/lib/graphiti/sideload/belongs_to.rb +2 -2
  65. data/lib/graphiti/sideload/has_many.rb +1 -1
  66. data/lib/graphiti/sideload/many_to_many.rb +18 -2
  67. data/lib/graphiti/sideload/polymorphic_belongs_to.rb +14 -8
  68. data/lib/graphiti/stats/dsl.rb +7 -1
  69. data/lib/graphiti/tasks.rb +10 -10
  70. data/lib/graphiti/types.rb +98 -98
  71. data/lib/graphiti/util/attribute_check.rb +1 -1
  72. data/lib/graphiti/util/class.rb +3 -3
  73. data/lib/graphiti/util/field_params.rb +1 -1
  74. data/lib/graphiti/util/hash.rb +19 -1
  75. data/lib/graphiti/util/link.rb +15 -19
  76. data/lib/graphiti/util/persistence.rb +17 -22
  77. data/lib/graphiti/util/relationship_payload.rb +2 -2
  78. data/lib/graphiti/util/remote_params.rb +12 -12
  79. data/lib/graphiti/util/remote_serializer.rb +2 -2
  80. data/lib/graphiti/util/serializer_attributes.rb +20 -25
  81. data/lib/graphiti/util/serializer_relationships.rb +17 -18
  82. data/lib/graphiti/util/sideload.rb +1 -1
  83. data/lib/graphiti/util/transaction_hooks_recorder.rb +3 -2
  84. data/lib/graphiti/version.rb +1 -1
  85. metadata +22 -6
@@ -12,7 +12,7 @@ class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
12
12
 
13
13
  def base_filter(parents)
14
14
  parent_ids = ids_for_parents(parents)
15
- { primary_key => parent_ids.join(',') }
15
+ {primary_key => parent_ids.join(",")}
16
16
  end
17
17
 
18
18
  def ids_for_parents(parents)
@@ -28,7 +28,7 @@ class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
28
28
  else
29
29
  model = resource.model
30
30
  namespace = namespace_for(model)
31
- model_name = model.name.gsub("#{namespace}::", '')
31
+ model_name = model.name.gsub("#{namespace}::", "")
32
32
  :"#{model_name.underscore}_id"
33
33
  end
34
34
  end
@@ -11,7 +11,7 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
11
11
  end
12
12
 
13
13
  def base_filter(parents)
14
- { foreign_key => ids_for_parents(parents).join(',') }
14
+ {foreign_key => ids_for_parents(parents).join(",")}
15
15
  end
16
16
 
17
17
  private
@@ -12,17 +12,33 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
12
12
  end
13
13
 
14
14
  def base_filter(parents)
15
- { true_foreign_key => ids_for_parents(parents).join(',') }
15
+ {true_foreign_key => ids_for_parents(parents).join(",")}
16
16
  end
17
17
 
18
18
  def infer_foreign_key
19
- raise 'You must explicitly pass :foreign_key for many-to-many relationships, or override in subclass to return a hash.'
19
+ raise "You must explicitly pass :foreign_key for many-to-many relationships, or override in subclass to return a hash."
20
20
  end
21
21
 
22
22
  def performant_assign?
23
23
  false
24
24
  end
25
25
 
26
+ # Override in subclass
27
+ def polymorphic?
28
+ false
29
+ end
30
+
31
+ def apply_belongs_to_many_filter
32
+ self_ref = self
33
+ fk_type = parent_resource_class.attributes[:id][:type]
34
+ fk_type = :hash if polymorphic?
35
+ resource_class.filter true_foreign_key, fk_type do
36
+ eq do |scope, value|
37
+ self_ref.belongs_to_many_filter(scope, value)
38
+ end
39
+ end
40
+ end
41
+
26
42
  def assign_each(parent, children)
27
43
  children.select do |c|
28
44
  match = ->(ct) { ct.send(true_foreign_key) == parent.send(primary_key) }
@@ -7,15 +7,21 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
7
7
  @calls = []
8
8
  end
9
9
 
10
+ # rubocop: disable Style/MethodMissingSuper
10
11
  def method_missing(name, *args, &blk)
11
12
  @calls << [name, args, blk]
12
13
  end
14
+ # rubocop: enable Style/MethodMissingSuper
15
+
16
+ def respond_to_missing?(*args)
17
+ true
18
+ end
13
19
  end
14
20
 
15
21
  class Grouper
16
22
  attr_reader :field_name
17
23
 
18
- def initialize(field_name, opts={})
24
+ def initialize(field_name, opts = {})
19
25
  @field_name = field_name
20
26
  @groups = []
21
27
  @except = Array(opts[:except]).map(&:to_sym)
@@ -50,10 +56,10 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
50
56
  args = call[1]
51
57
  opts = args.extract_options!
52
58
  opts.merge! as: sideload.name,
53
- parent: sideload,
54
- group_name: group.name,
55
- polymorphic_child: true
56
- if !sideload.resource.class.abstract_class?
59
+ parent: sideload,
60
+ group_name: group.name,
61
+ polymorphic_child: true
62
+ unless sideload.resource.class.abstract_class?
57
63
  opts[:foreign_key] ||= sideload.foreign_key
58
64
  opts[:primary_key] ||= sideload.primary_key
59
65
  end
@@ -76,9 +82,9 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
76
82
  :"#{name}_id"
77
83
  end
78
84
 
79
- def self.group_by(name, opts={}, &blk)
85
+ def self.group_by(name, opts = {}, &blk)
80
86
  self.grouper = Grouper.new(name, opts)
81
- self.grouper.instance_eval(&blk)
87
+ grouper.instance_eval(&blk)
82
88
  end
83
89
 
84
90
  def initialize(name, opts)
@@ -98,7 +104,7 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
98
104
  next if group_name.nil? || grouper.ignore?(group_name)
99
105
 
100
106
  match = ->(c) { c.group_name == group_name.to_sym }
101
- if sideload = children.values.find(&match)
107
+ if (sideload = children.values.find(&match))
102
108
  query = remove_invalid_sideloads(sideload.resource, query)
103
109
  sideload.resolve(group, query, graph_parent)
104
110
  else
@@ -30,7 +30,7 @@ module Graphiti
30
30
  # @param [Adapters::Abstract] adapter the Resource adapter
31
31
  # @param [Symbol, Hash] config example: +:total+ or +{ total: [:count] }+
32
32
  def initialize(adapter, config)
33
- config = { config => [] } if config.is_a?(Symbol)
33
+ config = {config => []} if config.is_a?(Symbol)
34
34
 
35
35
  @adapter = adapter
36
36
  @calculations = {}
@@ -46,9 +46,15 @@ module Graphiti
46
46
  #
47
47
  # ...will hit +method_missing+ and store the proc for future reference.
48
48
  # @api private
49
+ # rubocop: disable Style/MethodMissingSuper
49
50
  def method_missing(meth, *args, &blk)
50
51
  @calculations[meth] = blk
51
52
  end
53
+ # rubocop: enable Style/MethodMissingSuper
54
+
55
+ def respond_to_missing?(*args)
56
+ true
57
+ end
52
58
 
53
59
  # Grab a calculation proc. Raises error if no corresponding stat
54
60
  # has been configured.
@@ -10,25 +10,25 @@ namespace :graphiti do
10
10
  end
11
11
 
12
12
  def make_request(path, debug = false)
13
- if path.split('/').length == 2
13
+ if path.split("/").length == 2
14
14
  path = "#{ApplicationResource.endpoint_namespace}#{path}"
15
15
  end
16
- if path.include?('?')
17
- path << '&cache=bust'
16
+ path << if path.include?("?")
17
+ "&cache=bust"
18
18
  else
19
- path << '?cache=bust'
19
+ "?cache=bust"
20
20
  end
21
21
  path = "#{path}&debug=true" if debug
22
- session.get("#{path}")
22
+ session.get(path.to_s)
23
23
  JSON.parse(session.response.body)
24
24
  end
25
25
 
26
26
  desc "Execute request without web server."
27
- task :request, [:path,:debug] => [:environment] do |_, args|
27
+ task :request, [:path, :debug] => [:environment] do |_, args|
28
28
  setup_rails!
29
29
  Graphiti.logger = Graphiti.stdout_logger
30
30
  Graphiti::Debugger.preserve = true
31
- require 'pp'
31
+ require "pp"
32
32
  path, debug = args[:path], args[:debug]
33
33
  puts "Graphiti Request: #{path}"
34
34
  json = make_request(path, debug)
@@ -37,13 +37,13 @@ namespace :graphiti do
37
37
  end
38
38
 
39
39
  desc "Execute benchmark without web server."
40
- task :benchmark, [:path,:requests] => [:environment] do |_, args|
40
+ task :benchmark, [:path, :requests] => [:environment] do |_, args|
41
41
  setup_rails!
42
- took = Benchmark.ms do
42
+ took = Benchmark.ms {
43
43
  args[:requests].to_i.times do
44
44
  make_request(args[:path])
45
45
  end
46
- end
46
+ }
47
47
  puts "Took: #{(took / args[:requests].to_f).round(2)}ms"
48
48
  end
49
49
  end
@@ -5,81 +5,81 @@ module Graphiti
5
5
  definition.constructor(&blk)
6
6
  end
7
7
 
8
- WriteDateTime = create(::DateTime) do |input|
8
+ WriteDateTime = create(::DateTime) { |input|
9
9
  if input.is_a?(::Date) || input.is_a?(::Time)
10
10
  input = if input.respond_to?(:to_datetime)
11
- input.to_datetime
12
- else
13
- ::DateTime.parse(input.to_s)
14
- end
11
+ input.to_datetime
12
+ else
13
+ ::DateTime.parse(input.to_s)
14
+ end
15
15
  end
16
- input = Dry::Types['json.date_time'][input]
17
- Dry::Types['strict.date_time'][input] if input
18
- end
16
+ input = Dry::Types["json.date_time"][input]
17
+ Dry::Types["strict.date_time"][input] if input
18
+ }
19
19
 
20
- ReadDateTime = create(::DateTime) do |input|
20
+ ReadDateTime = create(::DateTime) { |input|
21
21
  if input.is_a?(::Date) || input.is_a?(::Time)
22
22
  input = if input.respond_to?(:to_datetime)
23
- input.to_datetime
24
- else
25
- ::DateTime.parse(input.to_s)
26
- end
23
+ input.to_datetime
24
+ else
25
+ ::DateTime.parse(input.to_s)
26
+ end
27
27
  end
28
- input = Dry::Types['json.date_time'][input]
29
- Dry::Types['strict.date_time'][input].iso8601 if input
30
- end
28
+ input = Dry::Types["json.date_time"][input]
29
+ Dry::Types["strict.date_time"][input].iso8601 if input
30
+ }
31
31
 
32
- PresentParamsDateTime = create(::DateTime) do |input|
33
- input = Dry::Types['params.date_time'][input]
34
- Dry::Types['strict.date_time'][input]
35
- end
32
+ PresentParamsDateTime = create(::DateTime) { |input|
33
+ input = Dry::Types["params.date_time"][input]
34
+ Dry::Types["strict.date_time"][input]
35
+ }
36
36
 
37
- Date = create(::Date) do |input|
37
+ Date = create(::Date) { |input|
38
38
  input = ::Date.parse(input.to_s) if input.is_a?(::Time)
39
- input = Dry::Types['json.date'][input]
40
- Dry::Types['strict.date'][input] if input
41
- end
39
+ input = Dry::Types["json.date"][input]
40
+ Dry::Types["strict.date"][input] if input
41
+ }
42
42
 
43
- PresentDate = create(::Date) do |input|
43
+ PresentDate = create(::Date) { |input|
44
44
  input = ::Date.parse(input.to_s) if input.is_a?(::Time)
45
- input = Dry::Types['json.date'][input]
46
- Dry::Types['strict.date'][input]
47
- end
45
+ input = Dry::Types["json.date"][input]
46
+ Dry::Types["strict.date"][input]
47
+ }
48
48
 
49
- Bool = create(nil) do |input|
50
- input = Dry::Types['params.bool'][input]
51
- Dry::Types['strict.bool'][input] unless input.nil?
52
- end
49
+ Bool = create(nil) { |input|
50
+ input = Dry::Types["params.bool"][input]
51
+ Dry::Types["strict.bool"][input] unless input.nil?
52
+ }
53
53
 
54
- PresentBool = create(nil) do |input|
55
- input = Dry::Types['params.bool'][input]
56
- Dry::Types['strict.bool'][input]
57
- end
54
+ PresentBool = create(nil) { |input|
55
+ input = Dry::Types["params.bool"][input]
56
+ Dry::Types["strict.bool"][input]
57
+ }
58
58
 
59
- Integer = create(::Integer) do |input|
60
- Dry::Types['coercible.integer'][input] if input
61
- end
59
+ Integer = create(::Integer) { |input|
60
+ Dry::Types["coercible.integer"][input] if input
61
+ }
62
62
 
63
63
  # The Float() check here is to ensure we have a number
64
64
  # Otherwise BigDecimal('foo') *will return a decimal*
65
- ParamDecimal = create(::BigDecimal) do |input|
65
+ ParamDecimal = create(::BigDecimal) { |input|
66
66
  Float(input)
67
- input = Dry::Types['coercible.decimal'][input]
68
- Dry::Types['strict.decimal'][input]
69
- end
67
+ input = Dry::Types["coercible.decimal"][input]
68
+ Dry::Types["strict.decimal"][input]
69
+ }
70
70
 
71
- PresentInteger = create(::Integer) do |input|
72
- Dry::Types['coercible.integer'][input]
73
- end
71
+ PresentInteger = create(::Integer) { |input|
72
+ Dry::Types["coercible.integer"][input]
73
+ }
74
74
 
75
- Float = create(::Float) do |input|
76
- Dry::Types['coercible.float'][input] if input
77
- end
75
+ Float = create(::Float) { |input|
76
+ Dry::Types["coercible.float"][input] if input
77
+ }
78
78
 
79
- PresentParamsHash = create(::Hash) do |input|
79
+ PresentParamsHash = create(::Hash) { |input|
80
80
  input = JSON.parse(input) if input.is_a?(String)
81
- Dry::Types['params.hash'][input]
82
- end
81
+ Dry::Types["params.hash"][input]
82
+ }
83
83
 
84
84
  REQUIRED_KEYS = [:params, :read, :write, :kind, :description]
85
85
 
@@ -88,82 +88,82 @@ module Graphiti
88
88
  hash = {
89
89
  integer_id: {
90
90
  canonical_name: :integer,
91
- params: Dry::Types['coercible.integer'],
92
- read: Dry::Types['coercible.string'],
93
- write: Dry::Types['coercible.integer'],
94
- kind: 'scalar',
95
- description: 'Base Type. Query/persist as integer, render as string.'
91
+ params: Dry::Types["coercible.integer"],
92
+ read: Dry::Types["coercible.string"],
93
+ write: Dry::Types["coercible.integer"],
94
+ kind: "scalar",
95
+ description: "Base Type. Query/persist as integer, render as string.",
96
96
  },
97
97
  uuid: {
98
- params: Dry::Types['coercible.string'],
99
- read: Dry::Types['coercible.string'],
100
- write: Dry::Types['coercible.string'],
101
- kind: 'scalar',
102
- description: 'Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.'
98
+ params: Dry::Types["coercible.string"],
99
+ read: Dry::Types["coercible.string"],
100
+ write: Dry::Types["coercible.string"],
101
+ kind: "scalar",
102
+ description: "Base Type. Like a normal string, but by default only eq/!eq and case-sensitive.",
103
103
  },
104
104
  string: {
105
- params: Dry::Types['coercible.string'],
106
- read: Dry::Types['coercible.string'],
107
- write: Dry::Types['coercible.string'],
108
- kind: 'scalar',
109
- description: 'Base Type.'
105
+ params: Dry::Types["coercible.string"],
106
+ read: Dry::Types["coercible.string"],
107
+ write: Dry::Types["coercible.string"],
108
+ kind: "scalar",
109
+ description: "Base Type.",
110
110
  },
111
111
  integer: {
112
112
  params: PresentInteger,
113
113
  read: Integer,
114
114
  write: Integer,
115
- kind: 'scalar',
116
- description: 'Base Type.'
115
+ kind: "scalar",
116
+ description: "Base Type.",
117
117
  },
118
118
  big_decimal: {
119
119
  params: ParamDecimal,
120
- read: Dry::Types['json.decimal'],
121
- write: Dry::Types['json.decimal'],
122
- kind: 'scalar',
123
- description: 'Base Type.'
120
+ read: Dry::Types["json.decimal"],
121
+ write: Dry::Types["json.decimal"],
122
+ kind: "scalar",
123
+ description: "Base Type.",
124
124
  },
125
125
  float: {
126
- params: Dry::Types['coercible.float'],
126
+ params: Dry::Types["coercible.float"],
127
127
  read: Float,
128
128
  write: Float,
129
- kind: 'scalar',
130
- description: 'Base Type.'
129
+ kind: "scalar",
130
+ description: "Base Type.",
131
131
  },
132
132
  boolean: {
133
133
  params: PresentBool,
134
134
  read: Bool,
135
135
  write: Bool,
136
- kind: 'scalar',
137
- description: 'Base Type.'
136
+ kind: "scalar",
137
+ description: "Base Type.",
138
138
  },
139
139
  date: {
140
140
  params: PresentDate,
141
141
  read: Date,
142
142
  write: Date,
143
- kind: 'scalar',
144
- description: 'Base Type.'
143
+ kind: "scalar",
144
+ description: "Base Type.",
145
145
  },
146
146
  datetime: {
147
147
  params: PresentParamsDateTime,
148
148
  read: ReadDateTime,
149
149
  write: WriteDateTime,
150
- kind: 'scalar',
151
- description: 'Base Type.'
150
+ kind: "scalar",
151
+ description: "Base Type.",
152
152
  },
153
153
  hash: {
154
154
  params: PresentParamsHash,
155
- read: Dry::Types['strict.hash'],
156
- write: Dry::Types['strict.hash'],
157
- kind: 'record',
158
- description: 'Base Type.'
155
+ read: Dry::Types["strict.hash"],
156
+ write: Dry::Types["strict.hash"],
157
+ kind: "record",
158
+ description: "Base Type.",
159
159
  },
160
160
  array: {
161
- params: Dry::Types['strict.array'],
162
- read: Dry::Types['strict.array'],
163
- write: Dry::Types['strict.array'],
164
- kind: 'array',
165
- description: 'Base Type.'
166
- }
161
+ params: Dry::Types["strict.array"],
162
+ read: Dry::Types["strict.array"],
163
+ write: Dry::Types["strict.array"],
164
+ kind: "array",
165
+ description: "Base Type.",
166
+ },
167
167
  }
168
168
 
169
169
  hash.each_pair do |k, v|
@@ -176,12 +176,12 @@ module Graphiti
176
176
 
177
177
  arrays[:"array_of_#{name.to_s.pluralize}"] = {
178
178
  canonical_name: name,
179
- params: Dry::Types['strict.array'].of(map[:params]),
180
- read: Dry::Types['strict.array'].of(map[:read]),
181
- test: Dry::Types['strict.array'].of(map[:test]),
182
- write: Dry::Types['strict.array'].of(map[:write]),
183
- kind: 'array',
184
- description: 'Base Type.'
179
+ params: Dry::Types["strict.array"].of(map[:params]),
180
+ read: Dry::Types["strict.array"].of(map[:read]),
181
+ test: Dry::Types["strict.array"].of(map[:test]),
182
+ write: Dry::Types["strict.array"].of(map[:write]),
183
+ kind: "array",
184
+ description: "Base Type.",
185
185
  }
186
186
  end
187
187
  hash.merge!(arrays)