graphiti 2.0.0.beta.11 → 2.0.0.beta.13

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -0
  3. data/CHANGELOG.md +39 -0
  4. data/Gemfile +3 -0
  5. data/lib/generators/graphiti/generator_mixin.rb +5 -4
  6. data/lib/generators/graphiti/install_generator.rb +15 -31
  7. data/lib/generators/graphiti/locale_generator.rb +86 -0
  8. data/lib/generators/graphiti/resource_generator.rb +2 -1
  9. data/lib/generators/graphiti/templates/application_resource.rb.erb +6 -9
  10. data/lib/generators/graphiti/templates/controller.rb.erb +1 -1
  11. data/lib/generators/graphiti/templates/locale.yml.erb +30 -0
  12. data/lib/generators/graphiti/templates/resource_writes_spec.rb.erb +1 -1
  13. data/lib/graphiti/adapters/active_record.rb +1 -1
  14. data/lib/graphiti/context_hash.rb +21 -0
  15. data/lib/graphiti/debugger.rb +3 -3
  16. data/lib/graphiti/error_serializers/conflict_request.rb +1 -1
  17. data/lib/graphiti/error_serializers/deprecated_constants.rb +6 -16
  18. data/lib/graphiti/error_serializers/invalid_request.rb +3 -1
  19. data/lib/graphiti/error_serializers/translated_title.rb +13 -0
  20. data/lib/graphiti/error_serializers/validation.rb +14 -4
  21. data/lib/graphiti/query.rb +8 -1
  22. data/lib/graphiti/rails/exception_handlers.rb +17 -0
  23. data/lib/graphiti/rails/railtie.rb +11 -1
  24. data/lib/graphiti/rails/rake_helpers.rb +5 -0
  25. data/lib/graphiti/rails.rb +18 -0
  26. data/lib/graphiti/request_validators/update_validator.rb +2 -6
  27. data/lib/graphiti/request_validators/validator.rb +6 -6
  28. data/lib/graphiti/resource.rb +3 -3
  29. data/lib/graphiti/resource_proxy.rb +14 -10
  30. data/lib/graphiti/schema/check.rb +69 -0
  31. data/lib/graphiti/schema.rb +8 -9
  32. data/lib/graphiti/scope.rb +4 -5
  33. data/lib/graphiti/sideload.rb +46 -9
  34. data/lib/graphiti/spec_helpers/matchers.rb +2 -1
  35. data/lib/graphiti/spec_helpers/rspec.rb +5 -13
  36. data/lib/graphiti/util/serializer_relationships.rb +1 -10
  37. data/lib/graphiti/util/simple_errors.rb +26 -5
  38. data/lib/graphiti/util/transaction_hooks_recorder.rb +2 -2
  39. data/lib/graphiti/version.rb +1 -1
  40. data/lib/graphiti.rb +9 -4
  41. data/lib/tasks/graphiti.rake +18 -0
  42. metadata +6 -1
@@ -13,11 +13,7 @@ module Graphiti
13
13
 
14
14
  def attribute_mismatch(attr_path)
15
15
  @error_class = Graphiti::Errors::ConflictRequest
16
- @errors.add(
17
- attr_path.join("."),
18
- :attribute_mismatch,
19
- message: "does not match the server endpoint"
20
- )
16
+ @errors.add(attr_path.join("."), :attribute_mismatch)
21
17
  end
22
18
 
23
19
  def required_payload?
@@ -32,7 +28,7 @@ module Graphiti
32
28
  end
33
29
 
34
30
  def payload_matches_endpoint?
35
- unless @params.dig(:data, :id) == @params.dig(:filter, :id)
31
+ unless @params.dig(:data, :id).to_s == @params.dig(:filter, :id).to_s
36
32
  attribute_mismatch([:data, :id])
37
33
  end
38
34
 
@@ -54,14 +54,14 @@ module Graphiti
54
54
  page = @params[:page]
55
55
  return if page.nil? || page.respond_to?(:each_pair)
56
56
 
57
- @errors.add(:page, :invalid, message: "must be an object")
57
+ @errors.add(:page, :invalid)
58
58
  end
59
59
 
60
60
  def validate_data_shape
61
61
  data = @params[:data]
62
62
  return true if data.nil? || data.respond_to?(:each_pair)
63
63
 
64
- @errors.add(:data, :invalid, message: "must be an object")
64
+ @errors.add(:data, :invalid)
65
65
  false
66
66
  end
67
67
 
@@ -69,7 +69,7 @@ module Graphiti
69
69
  relationships.each_key do |name|
70
70
  unless resource.class.sideload(name.to_sym)
71
71
  full_key = fully_qualified_key(name, payload_path, :relationships)
72
- @errors.add(full_key, :invalid_relationship, message: "is not a valid relationship")
72
+ @errors.add(full_key, :invalid_relationship)
73
73
  end
74
74
  end
75
75
 
@@ -110,11 +110,11 @@ module Graphiti
110
110
  begin
111
111
  attributes[key] = resource.typecast(key, value, :writable)
112
112
  rescue Graphiti::Errors::UnknownAttribute
113
- @errors.add(fully_qualified_key(key, payload_path), :unknown_attribute, message: "is an unknown attribute")
113
+ @errors.add(fully_qualified_key(key, payload_path), :unknown_attribute)
114
114
  rescue Graphiti::Errors::InvalidAttributeAccess
115
- @errors.add(fully_qualified_key(key, payload_path), :unwritable_attribute, message: "cannot be written")
115
+ @errors.add(fully_qualified_key(key, payload_path), :unwritable_attribute)
116
116
  rescue Graphiti::Errors::TypecastFailed => e
117
- @errors.add(fully_qualified_key(key, payload_path), :type_error, message: "should be type #{e.type_name}")
117
+ @errors.add(fully_qualified_key(key, payload_path), :type_error, type: e.type_name)
118
118
  end
119
119
  end
120
120
  end
@@ -37,8 +37,8 @@ module Graphiti
37
37
  end
38
38
  end
39
39
 
40
- def with_context(object, namespace = nil)
41
- Graphiti.with_context(object, namespace) do
40
+ def with_context(object, action = nil)
41
+ Graphiti.with_context(object, action) do
42
42
  yield
43
43
  end
44
44
  end
@@ -55,7 +55,7 @@ module Graphiti
55
55
  # is called, not a fixed list. Persistence overrides it with :create/:update
56
56
  # while saving, and :show while resolving sideloads afterwards.
57
57
  def self.current_action
58
- Graphiti.context[:namespace]
58
+ Graphiti.context[:action]
59
59
  end
60
60
 
61
61
  def current_action
@@ -159,7 +159,7 @@ module Graphiti
159
159
  params = normalized_params_copy(params)
160
160
  add_endpoint_filter(params, action)
161
161
  validator = ::Graphiti::RequestValidator.new(@resource, params, action)
162
- validator.validate!
162
+ with_context_action(action) { validator.validate! }
163
163
 
164
164
  if @assigned_model && same_write_payload?(validator.deserialized_payload)
165
165
  return @assigned_model
@@ -174,32 +174,36 @@ module Graphiti
174
174
  )
175
175
  end
176
176
 
177
+ # TODO: remove this. Only used for persisting many-to-many with AR
178
+ # (see activerecord adapter)
179
+ def with_context_action(action)
180
+ original = Graphiti.context[:action]
181
+ Graphiti.context[:action] = action
182
+ yield
183
+ ensure
184
+ Graphiti.context[:action] = original
185
+ end
186
+
177
187
  def save(action: :create)
178
- # TODO: remove this. Only used for persisting many-to-many with AR
179
- # (see activerecord adapter)
180
- original = Graphiti.context[:namespace]
181
- begin
182
- Graphiti.context[:namespace] = action
188
+ validator = with_context_action(action) do
183
189
  # An assigned model can only come from #assign_attributes, which
184
190
  # validated the payload it stored - re-validating here would run the
185
191
  # writable guards (and their guard_model lookups) a redundant time.
186
192
  unless @assigned_model
187
193
  ::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
188
194
  end
189
- validator = persist {
195
+ persist {
190
196
  @resource.persist_with_relationships \
191
197
  @payload.meta(action: action),
192
198
  @payload.attributes,
193
199
  @payload.relationships,
194
200
  assigned_model: @assigned_model
195
201
  }
196
- ensure
197
- Graphiti.context[:namespace] = original
198
202
  end
199
203
  @data, success = validator.to_a
200
204
 
201
205
  if success
202
- # If the context namespace is `update` or `create`, certain
206
+ # If the context action is `update` or `create`, certain
203
207
  # adapters will cause N+1 validation calls, so lets explicitly
204
208
  # switch to a lookup context.
205
209
  Graphiti.with_context(Graphiti.context[:object], :show) do
@@ -0,0 +1,69 @@
1
+ module Graphiti
2
+ class Schema
3
+ class Check
4
+ attr_reader :path, :errors
5
+
6
+ def initialize(schema, path)
7
+ @path = path
8
+ @schema = schema
9
+ @generated = normalize(schema)
10
+ @committed = normalize(JSON.parse(File.read(path))) if File.exist?(path)
11
+ @errors = @committed ? SchemaDiff.new(@committed, @generated).compare : []
12
+ end
13
+
14
+ def missing?
15
+ @committed.nil?
16
+ end
17
+
18
+ def stale?
19
+ !missing? && @committed != @generated
20
+ end
21
+
22
+ def compatible?
23
+ errors.empty?
24
+ end
25
+
26
+ def ok?
27
+ !missing? && !stale? && compatible?
28
+ end
29
+
30
+ def write!
31
+ FileUtils.mkdir_p(File.dirname(path))
32
+ File.write(path, JSON.pretty_generate(@schema))
33
+ path
34
+ end
35
+
36
+ def message
37
+ return "Schema is up to date: #{path}" if ok?
38
+ return "#{missing_message}\n\n#{regenerate}" if missing?
39
+ return "#{incompatible_message}\n\n#{errors.join("\n")}" unless compatible?
40
+
41
+ "Schema file is outdated: #{path}\n\n#{regenerate}"
42
+ end
43
+
44
+ private
45
+
46
+ def normalize(schema)
47
+ JSON.parse(JSON.generate(schema))
48
+ end
49
+
50
+ def missing_message
51
+ "Schema file not found: #{path}"
52
+ end
53
+
54
+ def incompatible_message
55
+ <<~MSG.chomp
56
+ Found backwards-incompatibilities in schema: #{path}
57
+
58
+ Re-run with FORCE_SCHEMA=true to accept them and overwrite the file.
59
+
60
+ Incompatibilities:
61
+ MSG
62
+ end
63
+
64
+ def regenerate
65
+ "Run `rake graphiti:schema:generate` and commit the file."
66
+ end
67
+ end
68
+ end
69
+ end
@@ -11,19 +11,18 @@ module Graphiti
11
11
  new(resources).generate
12
12
  end
13
13
 
14
- def self.generate!(resources = nil)
15
- schema = generate(resources)
14
+ def self.generate!(resources = nil, path: Graphiti.config.schema_path, force: ENV["FORCE_SCHEMA"] == "true")
15
+ result = check(resources, path: path)
16
+ return result.errors if !force && !result.compatible?
16
17
 
17
- if ENV["FORCE_SCHEMA"] != "true" && File.exist?(Graphiti.config.schema_path)
18
- old = JSON.parse(File.read(Graphiti.config.schema_path))
19
- errors = Graphiti::SchemaDiff.new(old, schema).compare
20
- return errors if errors.any?
21
- end
22
- FileUtils.mkdir_p(Graphiti.config.schema_path.to_s.gsub("/schema.json", ""))
23
- File.write(Graphiti.config.schema_path, JSON.pretty_generate(schema))
18
+ result.write!
24
19
  []
25
20
  end
26
21
 
22
+ def self.check(resources = nil, path: Graphiti.config.schema_path)
23
+ Check.new(generate(resources), path)
24
+ end
25
+
27
26
  def initialize(resources)
28
27
  @resources = resources.sort_by(&:name)
29
28
  @remote_resources = @resources.select(&:remote?)
@@ -39,18 +39,17 @@ module Graphiti
39
39
  !Graphiti.config.concurrency || on_pool_thread?
40
40
  end
41
41
 
42
- # TODO: move to Fiber[] once the floor is Ruby 3.2
43
42
  def self.on_pool_thread?
44
- Thread.current[POOL_THREAD] == true
43
+ Fiber[POOL_THREAD] == true
45
44
  end
46
45
 
47
46
  # Restores rather than clears because :caller_runs may have run the task on a request thread.
48
47
  def self.marking_pool_thread
49
- previous = Thread.current[POOL_THREAD]
50
- Thread.current[POOL_THREAD] = true
48
+ previous = Fiber[POOL_THREAD]
49
+ Fiber[POOL_THREAD] = true
51
50
  yield
52
51
  ensure
53
- Thread.current[POOL_THREAD] = previous
52
+ Fiber[POOL_THREAD] = previous
54
53
  end
55
54
 
56
55
  def initialize(object, resource, query, opts = {})
@@ -61,6 +61,27 @@ module Graphiti
61
61
  self.scope_proc = blk
62
62
  end
63
63
 
64
+ ASSIGNING_QUERY = :__graphiti_assigning_query
65
+ private_constant :ASSIGNING_QUERY
66
+
67
+ def self.assigning_node(query)
68
+ return yield if query.nil?
69
+
70
+ previous = Fiber[ASSIGNING_QUERY]
71
+ Fiber[ASSIGNING_QUERY] = query
72
+ yield
73
+ ensure
74
+ Fiber[ASSIGNING_QUERY] = previous unless query.nil?
75
+ end
76
+
77
+ def self.current_assigning_query
78
+ Fiber[ASSIGNING_QUERY]
79
+ end
80
+
81
+ def self.assigned?(query, record, association_name)
82
+ query.association_owners.key?([record.object_id, association_name])
83
+ end
84
+
64
85
  def self.assign(&blk)
65
86
  self.assign_proc = blk
66
87
  end
@@ -365,7 +386,7 @@ module Graphiti
365
386
 
366
387
  if self.class.scope_proc
367
388
  build_sideload_scope(parents, query, graph_parent).resolve do |sideload_results|
368
- fire_assign(parents, sideload_results)
389
+ fire_assign(parents, sideload_results, query)
369
390
  end
370
391
  else
371
392
  sync_load(parents, query, graph_parent, &proxy_block)
@@ -378,7 +399,7 @@ module Graphiti
378
399
 
379
400
  if self.class.scope_proc
380
401
  build_sideload_scope(parents, query, graph_parent).future_resolve do |sideload_results|
381
- fire_assign(parents, sideload_results)
402
+ fire_assign(parents, sideload_results, query)
382
403
  end
383
404
  else
384
405
  future_load(parents, query, graph_parent, &proxy_block)
@@ -413,10 +434,14 @@ module Graphiti
413
434
  end
414
435
 
415
436
  def associate_all(parent, children)
437
+ return unless claim_association(parent)
438
+
416
439
  parent_resource.associate_all(parent, children, association_name, type)
417
440
  end
418
441
 
419
442
  def associate(parent, child)
443
+ return unless claim_association(parent)
444
+
420
445
  parent_resource.associate(parent, child, association_name, type)
421
446
  end
422
447
 
@@ -506,7 +531,7 @@ module Graphiti
506
531
  opts[:sideload_parent_length] = parents.length
507
532
  opts[:query] = query
508
533
  opts[:after_resolve] = ->(results) {
509
- fire_assign(parents, results)
534
+ fire_assign(parents, results, query)
510
535
  }
511
536
  end
512
537
  end
@@ -519,16 +544,28 @@ module Graphiti
519
544
  end
520
545
  end
521
546
 
522
- def fire_assign(parents, children)
523
- with_error_handling Errors::SideloadAssignError do
524
- if self.class.assign_proc
525
- instance_exec(parents, children, &self.class.assign_proc)
526
- else
527
- assign(parents, children)
547
+ def fire_assign(parents, children, query = nil)
548
+ self.class.assigning_node(query) do
549
+ with_error_handling Errors::SideloadAssignError do
550
+ if self.class.assign_proc
551
+ instance_exec(parents, children, &self.class.assign_proc)
552
+ else
553
+ assign(parents, children)
554
+ end
528
555
  end
529
556
  end
530
557
  end
531
558
 
559
+ # A record can be shared by two nodes of the include tree, and a node that narrows
560
+ # differently returns different rows, so the first node to populate an association owns it.
561
+ def claim_association(parent)
562
+ query = self.class.current_assigning_query
563
+ return true if query.nil?
564
+
565
+ key = [parent.object_id, association_name]
566
+ query.association_owners.compute_if_absent(key) { query.hash } == query.hash
567
+ end
568
+
532
569
  def with_error_handling(error_class)
533
570
  begin
534
571
  result = yield
@@ -59,6 +59,7 @@ module Graphiti
59
59
 
60
60
  class RelationMatcher < BaseMatcher
61
61
  GRAPHITI_OPTS = %i[primary_key foreign_key resource readable writable link single].freeze
62
+ SIDELOAD_METHODS = {resource: :resource_class, readable: :readable?, writable: :writable?, single: :single?}.freeze
62
63
  GRAPHITI_CONFIG_KEY = :sideloads
63
64
  EXPECTED_ACTION = ""
64
65
 
@@ -73,7 +74,7 @@ module Graphiti
73
74
  attr_reader :target, :opts, :resource
74
75
 
75
76
  def assert_opt(opt)
76
- asserted_opt = (opt == :resource) ? :resource_class : opt
77
+ asserted_opt = SIDELOAD_METHODS.fetch(opt, opt)
77
78
  return true if config.send(asserted_opt) == opts[opt]
78
79
 
79
80
  @opt_failures << opt_failure_message(opt, opts[opt], config.send(asserted_opt))
@@ -127,22 +127,14 @@ module Graphiti
127
127
  end
128
128
  end
129
129
 
130
- def self.schema!(resources = nil)
130
+ def self.schema!(resources = nil, path: nil)
131
131
  ::RSpec.describe "Graphiti Schema" do
132
132
  it "generates a backwards-compatible schema" do
133
- message = <<~MSG
134
- Found backwards-incompatibilities in schema! Run with FORCE_SCHEMA=true to ignore.
133
+ check = Graphiti::Schema.check(resources, path: path || Graphiti.config.schema_path)
134
+ forced = ENV["FORCE_SCHEMA"] == "true"
135
+ check.write! if check.compatible? || forced
135
136
 
136
- Incompatibilities:
137
-
138
- MSG
139
-
140
- errors = Graphiti::Schema.generate!(resources)
141
- errors.each do |e|
142
- message << "#{e}\n"
143
- end
144
-
145
- expect(errors.empty?).to eq(true), message
137
+ expect(check.compatible? || forced).to eq(true), check.message
146
138
  end
147
139
  end
148
140
  end
@@ -71,7 +71,7 @@ module Graphiti
71
71
  # not predict, so the loaded records win. Only the un-included case
72
72
  # is worth short-circuiting.
73
73
  if sideload_ref.resource_ids_from_foreign_key? &&
74
- !self_ref.send(:included_anywhere?, @proxy.query.include_hash, sideload_ref.name)
74
+ !::Graphiti::Sideload.assigned?(@proxy.query, @object, sideload_ref.association_name)
75
75
  linkage always: sideload_ref.render_resource_ids? do
76
76
  foreign_key = begin
77
77
  @object.public_send(sideload_ref.foreign_key)
@@ -104,15 +104,6 @@ module Graphiti
104
104
  end
105
105
  end
106
106
 
107
- # A relationship nested under another one is still loaded and can still
108
- # be narrowed by a deep filter, so the foreign key is not a safe
109
- # stand-in for what the request actually returns.
110
- def included_anywhere?(include_hash, name)
111
- include_hash.any? do |key, nested|
112
- key == name || included_anywhere?(nested, name)
113
- end
114
- end
115
-
116
107
  def data_proc
117
108
  sideload_ref = @sideload
118
109
  resource_class_ref = @resource_class
@@ -6,6 +6,21 @@ module Graphiti
6
6
  class SimpleErrors
7
7
  include Enumerable
8
8
 
9
+ # Overridable under graphiti.errors.messages.
10
+ DEFAULT_MESSAGES = {
11
+ missing: "is missing",
12
+ invalid: "must be an object",
13
+ invalid_relationship: "is not a valid relationship",
14
+ unwritable_relationship: "cannot be written",
15
+ unknown_attribute: "is an unknown attribute",
16
+ unwritable_attribute: "cannot be written",
17
+ type_error: "should be type %{type}",
18
+ attribute_mismatch: "does not match the server endpoint"
19
+ }.freeze
20
+
21
+ # Joins an attribute to its message, like Rails' own errors.format.
22
+ DEFAULT_FORMAT = "%{attribute} %{message}"
23
+
9
24
  attr_reader :messages, :details
10
25
 
11
26
  def initialize(validation_target)
@@ -50,11 +65,9 @@ module Graphiti
50
65
  end
51
66
  alias_method :blank?, :empty?
52
67
 
53
- def add(attribute, code, message: nil)
54
- message ||= "is #{code.to_s.humanize.downcase}"
55
-
68
+ def add(attribute, code, message: nil, **interpolations)
56
69
  details[attribute.to_sym] << {error: code}
57
- messages[attribute.to_sym] << message
70
+ messages[attribute.to_sym] << translate(code, message, **interpolations, attribute: attribute)
58
71
  end
59
72
 
60
73
  def added?(attribute, code)
@@ -73,11 +86,19 @@ module Graphiti
73
86
 
74
87
  def full_message(attribute, message)
75
88
  return message if attribute == :base
76
- "#{attribute} #{message}"
89
+
90
+ translate(:format, DEFAULT_FORMAT, [:graphiti, :errors], attribute: attribute, message: message)
77
91
  end
78
92
 
79
93
  private
80
94
 
95
+ def translate(key, fallback, scope = [:graphiti, :errors, :messages], **interpolations)
96
+ fallback ||= DEFAULT_MESSAGES.fetch(key) { "is #{key.to_s.humanize.downcase}" }
97
+ return fallback % interpolations unless defined?(::I18n)
98
+
99
+ ::I18n.t(key, scope: scope, default: fallback, **interpolations)
100
+ end
101
+
81
102
  def apply_default_array(hash)
82
103
  hash.default_proc = proc { |h, key| h[key] = [] }
83
104
  hash
@@ -58,11 +58,11 @@ module Graphiti
58
58
  private
59
59
 
60
60
  def _hooks
61
- Thread.current[:_graphiti_hooks]
61
+ Fiber[:_graphiti_hooks]
62
62
  end
63
63
 
64
64
  def reset_hooks
65
- Thread.current[:_graphiti_hooks] = {
65
+ Fiber[:_graphiti_hooks] = {
66
66
  after_graph_persist: [],
67
67
  before_commit: [],
68
68
  after_commit: []
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "2.0.0.beta.11"
2
+ VERSION = "2.0.0.beta.13"
3
3
  end
data/lib/graphiti.rb CHANGED
@@ -51,18 +51,20 @@ module Graphiti
51
51
 
52
52
  # @api private
53
53
  def self.context
54
- Thread.current[:context] ||= {}
54
+ Fiber[:context] ||= ContextHash.new
55
55
  end
56
56
 
57
57
  # @api private
58
58
  def self.context=(val)
59
- Thread.current[:context] = val
59
+ Fiber[:context] = ContextHash.new.tap do |hash|
60
+ val.each { |key, value| hash[key] = value }
61
+ end
60
62
  end
61
63
 
62
64
  # @api private
63
- def self.with_context(obj, namespace = nil)
65
+ def self.with_context(obj, action = nil)
64
66
  prior = context
65
- self.context = {object: obj, namespace: namespace}
67
+ self.context = {object: obj, action: action}
66
68
  yield
67
69
  ensure
68
70
  self.context = prior
@@ -169,12 +171,14 @@ require "graphiti/version"
169
171
  require "graphiti/jsonapi_serializable_ext"
170
172
  require "graphiti/configuration"
171
173
  require "graphiti/context"
174
+ require "graphiti/context_hash"
172
175
  require "graphiti/errors"
173
176
  require "graphiti/types"
174
177
  require "graphiti/audit"
175
178
  require "graphiti/audit/report"
176
179
  require "graphiti/schema"
177
180
  require "graphiti/schema_diff"
181
+ require "graphiti/schema/check"
178
182
  require "graphiti/adapters/abstract"
179
183
  require "graphiti/resource/sideloading"
180
184
  require "graphiti/resource/links"
@@ -238,6 +242,7 @@ require "graphiti/query"
238
242
  require "graphiti/debugger"
239
243
  require "graphiti/util/cache_debug"
240
244
  require "graphiti/util/uri_decoder"
245
+ require "graphiti/error_serializers/translated_title"
241
246
  require "graphiti/error_serializers/validation"
242
247
  require "graphiti/error_serializers/invalid_request"
243
248
  require "graphiti/error_serializers/conflict_request"
@@ -16,6 +16,24 @@ namespace :graphiti do
16
16
  Graphiti::Debugger.flush if debug
17
17
  end
18
18
 
19
+ namespace :schema do
20
+ desc "Write the schema file. Refuses backwards-incompatible changes unless FORCE_SCHEMA=true. Takes an optional path, defaulting to Graphiti.config.schema_path."
21
+ task :generate, [:path] => [:environment] do |_, args|
22
+ check = Graphiti::Schema.check(path: helpers.schema_path(args[:path]))
23
+ abort check.message unless check.compatible? || ENV["FORCE_SCHEMA"] == "true"
24
+
25
+ puts "Schema written: #{check.write!}"
26
+ end
27
+
28
+ desc "Fail unless the committed schema file exists, is up to date, and is backwards-compatible. Takes an optional path, defaulting to Graphiti.config.schema_path."
29
+ task :check, [:path] => [:environment] do |_, args|
30
+ check = Graphiti::Schema.check(path: helpers.schema_path(args[:path]))
31
+ abort check.message unless check.ok?
32
+
33
+ puts check.message
34
+ end
35
+ end
36
+
19
37
  desc "Audit every relationship: what will raise, what loads to render ids, which render no ids, and which checks passed."
20
38
  task audit: [:environment] do
21
39
  helpers.setup_rails!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta.11
4
+ version: 2.0.0.beta.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
@@ -287,6 +287,7 @@ files:
287
287
  - lib/generators/graphiti/api_test_generator.rb
288
288
  - lib/generators/graphiti/generator_mixin.rb
289
289
  - lib/generators/graphiti/install_generator.rb
290
+ - lib/generators/graphiti/locale_generator.rb
290
291
  - lib/generators/graphiti/resource_generator.rb
291
292
  - lib/generators/graphiti/resource_test_generator.rb
292
293
  - lib/generators/graphiti/templates/application_resource.rb.erb
@@ -294,6 +295,7 @@ files:
294
295
  - lib/generators/graphiti/templates/create_request_spec.rb.erb
295
296
  - lib/generators/graphiti/templates/destroy_request_spec.rb.erb
296
297
  - lib/generators/graphiti/templates/index_request_spec.rb.erb
298
+ - lib/generators/graphiti/templates/locale.yml.erb
297
299
  - lib/generators/graphiti/templates/resource.rb.erb
298
300
  - lib/generators/graphiti/templates/resource_reads_spec.rb.erb
299
301
  - lib/generators/graphiti/templates/resource_writes_spec.rb.erb
@@ -316,12 +318,14 @@ files:
316
318
  - lib/graphiti/cli.rb
317
319
  - lib/graphiti/configuration.rb
318
320
  - lib/graphiti/context.rb
321
+ - lib/graphiti/context_hash.rb
319
322
  - lib/graphiti/debugger.rb
320
323
  - lib/graphiti/delegates/pagination.rb
321
324
  - lib/graphiti/deserializer.rb
322
325
  - lib/graphiti/error_serializers/conflict_request.rb
323
326
  - lib/graphiti/error_serializers/deprecated_constants.rb
324
327
  - lib/graphiti/error_serializers/invalid_request.rb
328
+ - lib/graphiti/error_serializers/translated_title.rb
325
329
  - lib/graphiti/error_serializers/validation.rb
326
330
  - lib/graphiti/errors.rb
327
331
  - lib/graphiti/extensions/boolean_attribute.rb
@@ -359,6 +363,7 @@ files:
359
363
  - lib/graphiti/responders.rb
360
364
  - lib/graphiti/runner.rb
361
365
  - lib/graphiti/schema.rb
366
+ - lib/graphiti/schema/check.rb
362
367
  - lib/graphiti/schema_diff.rb
363
368
  - lib/graphiti/scope.rb
364
369
  - lib/graphiti/scoping/base.rb