graphql_migrate_execution 1.0.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2835acca245bcfe0c626420ae2ce5ac96ee916e0a4dbb35a8db5f1c97a786e1b
4
- data.tar.gz: 4522d268937cf7a4fd925b4cf0a8fb492ec797006cf48e9972ae74847390eb21
3
+ metadata.gz: 9c879a6e266b7520abe76bf69a63cbc7ff8a446fe6438ad0ca37c89368f4c153
4
+ data.tar.gz: cb33f65e293a55a01d3d2e440eb23f40e2f5c3fc28f1241720f40cacf76ca5d4
5
5
  SHA512:
6
- metadata.gz: ad8ba9248a12a116f6e6170fbae55029914bd43b71e2aefcab54c85b87af4347e19cc3aa6a0ba87a9df2a1d7caf7594f901ae974e88208c82e473adc61378e3e
7
- data.tar.gz: 128a44c801b750c31ad661af93745568a529389afb9fd738534125aba4a708d7c9ab7293b0e02cde6453473486763a09561e16892ac75c93fc39e91659e67672
6
+ metadata.gz: 99bdbfe4e36d7dce2a78ef1254d4bde1039d3c5189aa96df6c7dab18023ae1f3d58293e1dc98ae0f639f14ced08ebe4f930565d344ff2821fa765ae122c00d8f
7
+ data.tar.gz: 9e29f27f38e4603a2076071d91dd304c81239e39c59080dfdc533aa9a1f0dc11175bd354a4c924e13b9abd60ba18ba2a7ab79f5270ae6107015ee949cb1ec5ee
@@ -52,6 +52,15 @@ module GraphqlMigrateExecution
52
52
  else
53
53
  "objects.#{map_method} { |obj| obj#{call_chain} }"
54
54
  end
55
+ when /([A-Z][a-zA-Z_0-9]*(\.|\[)[:a-zA-Z0-9_\.\"\'\[\]]+)/
56
+ # Constant call
57
+ "objects.#{map_method} { |_obj| #{$1} }"
58
+ when /^("[a-zA-Z0-9 :_\.]+"|:[a-zA-Z0-9_]+)$/
59
+ # Constant literal
60
+ "Array.new(objects.size, #{$1})"
61
+ when /^([a-z_0-9.]+)$/
62
+ # Local variable or receiverless method, possibly followed by a method chain
63
+ "Array.new(objects.size, #{$1})"
55
64
  else
56
65
  raise ArgumentError, "Failed to transform Dataloader argument: #{old_load_arg_s.inspect}"
57
66
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ module GraphqlMigrateExecution
3
+ # Turns `fallback_value: ...` into `resolve_static: true`
4
+ class FallbackValue < Strategy
5
+ self.color = :GREEN
6
+
7
+ def migrate(field_definition)
8
+ indent = field_definition.node.location.slice_lines[/^ +/]
9
+ method_name = self.class.prefix_if_necessary(field_definition.name)
10
+ is_interface = field_definition.type_definition.is_interface
11
+ new_body = "\n".dup
12
+
13
+ if is_interface
14
+ method_prefix = "def #{method_name}"
15
+ new_body << "#{indent}resolver_methods do\n"
16
+ method_indent = indent + " "
17
+ else
18
+ method_prefix = "def self.#{method_name}"
19
+ method_indent = indent
20
+ end
21
+
22
+ new_body << "#{method_indent}#{method_prefix}(_context)\n"
23
+ new_body << method_indent + " #{field_definition.fallback_value}\n"
24
+ new_body << method_indent + "end"
25
+
26
+ if is_interface
27
+ new_body << "\n#{indent}end"
28
+ end
29
+
30
+ @result_source.sub!(field_definition.source, field_definition.source + "\n" + new_body)
31
+ keyword_v = method_name == field_definition.name.to_s ? true : method_name.to_sym.inspect
32
+ inject_field_keyword(field_definition, :resolve_static, keyword_v)
33
+ end
34
+
35
+ def cleanup(field_definition)
36
+ remove_field_keyword(field_definition, :fallback_value)
37
+ end
38
+ end
39
+ end
@@ -12,6 +12,7 @@ module GraphqlMigrateExecution
12
12
  @type_instance_method = nil
13
13
  @object_direct_method = nil
14
14
  @dig = nil
15
+ @fallback_value = nil
15
16
  @already_migrated = nil
16
17
 
17
18
  @resolver_method = nil
@@ -46,6 +47,8 @@ module GraphqlMigrateExecution
46
47
  ResolveStatic
47
48
  when :resolve_batch
48
49
  ResolveBatch
50
+ when :resolve_legacy_instance_method
51
+ DoNothing
49
52
  else
50
53
  raise ArgumentError, "Unexpected already_migrated: #{@already_migrated.inspect}"
51
54
  end
@@ -53,6 +56,8 @@ module GraphqlMigrateExecution
53
56
  resolver_method.migration_strategy
54
57
  when :resolver
55
58
  DoNothing
59
+ when :fallback_value
60
+ FallbackValue
56
61
  else
57
62
  raise "No migration strategy for resolve_mode #{@resolve_mode.inspect}"
58
63
  end
@@ -65,13 +70,13 @@ module GraphqlMigrateExecution
65
70
  end
66
71
 
67
72
  def future_resolve_shorthand
68
- method_name = resolver_method.name
73
+ method_name = Strategy.prefix_if_necessary(resolver_method.name).to_sym
69
74
  name == method_name ? true : method_name
70
75
  end
71
76
 
72
77
  attr_writer :resolve_mode
73
78
 
74
- attr_accessor :hash_key, :object_direct_method, :type_instance_method, :resolver, :dig, :already_migrated
79
+ attr_accessor :hash_key, :object_direct_method, :type_instance_method, :resolver, :dig, :already_migrated, :fallback_value
75
80
 
76
81
  def path
77
82
  @path ||= "#{type_definition.name}.#{@name}"
@@ -25,7 +25,11 @@ module GraphqlMigrateExecution
25
25
  attr_reader :colorable, :action_method, :implicit
26
26
 
27
27
  def run
28
- Dir.glob(@glob).each do |filepath|
28
+ files = Dir.glob(@glob)
29
+ if files.size.zero?
30
+ warn "No files found for #{@glob.inspect}"
31
+ end
32
+ files.each do |filepath|
29
33
  source = File.read(filepath)
30
34
  action = Action.new(self, filepath, source)
31
35
  action.run
@@ -11,6 +11,7 @@ module GraphqlMigrateExecution
11
11
 
12
12
  def cleanup(field_definition)
13
13
  remove_field_keyword(field_definition, :resolver_method)
14
+ remove_field_keyword(field_definition, :fallback_value)
14
15
  remove_resolver_method(field_definition)
15
16
  end
16
17
  end
@@ -139,7 +139,7 @@ module GraphqlMigrateExecution
139
139
  end
140
140
 
141
141
  def returns_string_hash?
142
- return_expressions.any? { |exp_node| exp_node.is_a?(Prism::HashNode) && exp_node.elements.all? { |el| el.key.is_a?(Prism::StringNode) } }
142
+ return_expressions.any? { |exp_node| exp_node.is_a?(Prism::HashNode) && exp_node.elements.size > 0 && exp_node.elements.all? { |el| el.key.is_a?(Prism::StringNode) } }
143
143
  end
144
144
 
145
145
  private
@@ -52,6 +52,23 @@ module GraphqlMigrateExecution
52
52
  attr_accessor :color
53
53
  end
54
54
 
55
+ # These methods conflict with built-in class methods,
56
+ # so when migrating them, prefix them.
57
+ METHODS_TO_RENAME = [
58
+ "name",
59
+ "fields",
60
+ "arguments",
61
+ ]
62
+
63
+ def self.prefix_if_necessary(method_name)
64
+ name_s = method_name.to_s
65
+ if METHODS_TO_RENAME.include?(name_s)
66
+ "resolve_#{method_name}"
67
+ else
68
+ name_s
69
+ end
70
+ end
71
+
55
72
  private
56
73
 
57
74
  def inject_resolve_keyword(field_definition, keyword)
@@ -65,10 +82,13 @@ module GraphqlMigrateExecution
65
82
  if field_definition_source.include?(pair)
66
83
  # Pass, don't re-add it
67
84
  elsif field_definition_source.include?("#{keyword}:")
68
- raise "Can't re-inject #{keyword} because it's already present in the definition:\n\n#{field_definition_source}"
85
+ warn "Can't re-inject #{keyword} because it's already present in the definition:\n\n#{field_definition_source}"
69
86
  else
70
87
  new_definition_source = if field_definition_source[/ [a-z_]+:/] # Does it already have keywords?
71
- field_definition_source.sub(/(field.+?)((?: do)|(?: {)|$)/, "\\1, #{pair}\\2")
88
+ field_definition_source.sub(/(field.+?)((?:,$)|(?: do)|(?: {)|$)/, "\\1, #{pair}\\2")
89
+ elsif (block_node = field_definition.node.block)
90
+ block_source = block_node.location.slice
91
+ field_definition_source.sub(" " + block_source, ", #{pair} #{block_source}")
72
92
  else
73
93
  field_definition_source + ", #{pair}"
74
94
  end
@@ -85,30 +105,48 @@ module GraphqlMigrateExecution
85
105
 
86
106
  def replace_resolver_method(field_definition, new_params)
87
107
  resolver_method = field_definition.resolver_method
88
- method_name = resolver_method.name
108
+ old_method_name = resolver_method.name.to_s
109
+ new_method_name = self.class.prefix_if_necessary(old_method_name)
89
110
  old_method = resolver_method.source
90
- new_class_method = old_method
91
- .sub("def ", 'def self.')
111
+
112
+ method_prefix = field_definition.type_definition.is_interface ? "def #{new_method_name}" : "def self.#{new_method_name}"
113
+ new_class_method = old_method.sub("def #{old_method_name}", method_prefix)
92
114
 
93
115
  if resolver_method.parameter_names.empty?
94
- new_class_method.sub!(method_name.to_s, "#{method_name}(#{new_params})")
116
+ new_class_method.sub!(new_method_name, "#{new_method_name}(#{new_params})")
95
117
  else
96
- new_class_method.sub!("def self.#{method_name}(", "def self.#{method_name}(#{new_params}, ")
118
+ new_class_method.sub!("#{method_prefix}(", "#{method_prefix}(#{new_params}, ")
119
+ end
120
+
121
+ if field_definition.type_definition.is_interface
122
+ indent = new_class_method[/\A +/]
123
+ new_class_method.gsub!(/^#{indent}/, "#{indent} ")
124
+ new_class_method = "#{indent}resolver_methods do\n#{new_class_method}#{indent}end\n"
97
125
  end
98
126
 
99
127
  old_lines = old_method.split("\n")
100
- new_body = old_lines.first[/^ +/] + " self.class.#{method_name}(#{new_params}#{resolver_method.parameter_names.map { |n| ", #{n}: #{n}"}.join})"
101
- new_inst_method = [old_lines.first, new_body, old_lines.last].join("\n")
128
+ forward_previous_params = resolver_method.parameter_names.map { |n| ", #{n}: #{n}"}.join
129
+ if old_lines.size == 1
130
+ # TODO doesn't support `def ... =` syntax
131
+ previous_def = /def [^;]+;/.match(old_method)
132
+ new_inst_method = old_lines.first[/^ +/] + "#{previous_def} self.class.#{new_method_name}(#{new_params}#{forward_previous_params}); end"
133
+ else
134
+ new_body = old_lines.first[/^ +/] + " self.class.#{new_method_name}(#{new_params}#{forward_previous_params})"
135
+ new_inst_method = [old_lines.first, new_body, old_lines.last].join("\n")
136
+ end
102
137
 
103
138
  new_double_definition = new_class_method + "\n" + new_inst_method + "\n"
104
139
  @result_source.sub!(old_method, new_double_definition)
105
140
  end
106
141
 
142
+
107
143
  def remove_resolver_method(field_definition)
108
- src_pattern = /(\n*)(#{Regexp.quote(field_definition.resolver_method.source)})(\n*)/
109
- @result_source.sub!(src_pattern) do
110
- # $2 includes a newline, too
111
- "#{$1.length > 1 ? "\n" : ""}#{$3.length > 0 ? "\n" : ""}"
144
+ if field_definition.resolver_method
145
+ src_pattern = /(\n*)(#{Regexp.quote(field_definition.resolver_method.source)})(\n*)/
146
+ @result_source.sub!(src_pattern) do
147
+ # $2 includes a newline, too
148
+ "#{$1.length > 1 ? "\n" : ""}#{$3.length > 0 ? "\n" : ""}"
149
+ end
112
150
  end
113
151
  end
114
152
  end
@@ -7,9 +7,10 @@ module GraphqlMigrateExecution
7
7
  @field_definitions = {}
8
8
  @resolver_methods = {}
9
9
  @is_resolver = false
10
+ @is_interface
10
11
  end
11
12
 
12
- attr_accessor :is_resolver, :migration
13
+ attr_accessor :is_resolver, :migration, :is_interface
13
14
 
14
15
  attr_reader :resolver_methods, :name, :field_definitions
15
16
 
@@ -1,3 +1,3 @@
1
1
  module GraphqlMigrateExecution
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -24,6 +24,7 @@ module GraphqlMigrateExecution
24
24
 
25
25
  def visit_module_node(node)
26
26
  td = @type_definitions[node.name]
27
+ td.is_interface = true
27
28
  @type_definition_stack << td
28
29
  super
29
30
  ensure
@@ -38,7 +39,7 @@ module GraphqlMigrateExecution
38
39
  when "hash_key"
39
40
  @current_field_definition.resolve_mode ||= :hash_key
40
41
  @current_field_definition.hash_key = get_keyword_value(assoc.value)
41
- when "resolver"
42
+ when "resolver", "mutation", "subscription"
42
43
  @current_field_definition.resolve_mode ||= :resolver
43
44
  @current_field_definition.resolver = get_keyword_value(assoc.value)
44
45
  when "method"
@@ -50,10 +51,15 @@ module GraphqlMigrateExecution
50
51
  when "dig"
51
52
  @current_field_definition.resolve_mode ||= :dig
52
53
  @current_field_definition.dig = get_keyword_value(assoc.value)
53
- when "resolve_each", "resolve_static", "resolve_batch"
54
+ when "resolve_each", "resolve_static", "resolve_batch", "resolve_legacy_instance_method"
54
55
  # These should override any other keywords that are discovered
55
56
  @current_field_definition.resolve_mode = :already_migrated
56
57
  @current_field_definition.already_migrated = { assoc.key.unescaped.to_sym => get_keyword_value(assoc.value) }
58
+ when "fallback_value"
59
+ if !assoc.value.is_a?(Prism::NilNode)
60
+ @current_field_definition.resolve_mode ||= :fallback_value
61
+ @current_field_definition.fallback_value = self.class.source_for_constant_node(assoc.value)
62
+ end
57
63
  else
58
64
  # fallback_value, connection, extensions, extras, resolver, mutation, subscription
59
65
  @current_field_definition.unknown_options << assoc.key.unescaped
@@ -65,11 +71,10 @@ module GraphqlMigrateExecution
65
71
  end
66
72
 
67
73
  def visit_call_node(node)
68
- if node.receiver.nil? && node.name == :field
74
+ if node.receiver.nil? && node.name == :field && node.arguments
69
75
  first_arg = node.arguments.arguments.first # rubocop:disable Development/ContextIsPassedCop
70
- if first_arg.is_a?(Prism::SymbolNode)
76
+ if first_arg.is_a?(Prism::SymbolNode) && (td = @type_definition_stack.last)
71
77
  field_name = first_arg.unescaped
72
- td = @type_definition_stack.last
73
78
  @current_field_definition = td.field_definition(field_name, node)
74
79
  else
75
80
  warn "GraphQL-Ruby warning: Skipping unrecognized field definition: #{node.inspect}"
@@ -113,20 +118,29 @@ module GraphqlMigrateExecution
113
118
  end
114
119
 
115
120
  def visit_def_node(node)
116
- if @is_public
117
- td = @type_definition_stack.last
121
+ if @is_public && (td = @type_definition_stack.last)
118
122
  if node.receiver.nil?
119
123
  @current_resolver_method = td.resolver_method(node.name, node)
120
124
  end
121
125
 
122
126
  if node.name == :resolve && td.resolver_methods.size == 1
123
127
  td.is_resolver = true
124
- elsif td.is_resolver && td.remove_resolver_methods.size > 1
128
+ elsif td.is_resolver && td.resolver_methods.size > 1
125
129
  td.is_resolver = false
126
130
  end
127
131
 
128
- body = node.body.body
129
- if @current_resolver_method && body.length == 1 && (call_node = body.first).is_a?(Prism::CallNode)
132
+ body = case node.body
133
+ when Prism::StatementsNode
134
+ node.body.body
135
+ when Prism::BeginNode
136
+ node.body.statements.body
137
+ when nil
138
+ nil
139
+ else
140
+ raise ArgumentError, "Unexpected DefNode body for `def #{node.name}`: #{node.body.class}"
141
+ end
142
+
143
+ if body && @current_resolver_method && body.length == 1 && (call_node = body.first).is_a?(Prism::CallNode)
130
144
  case call_node.name
131
145
  when :load, :request, :load_all, :request_all
132
146
  if (call_node2 = call_node.receiver).is_a?(Prism::CallNode) && call_node2.name == :with
@@ -9,6 +9,7 @@ require "graphql_migrate_execution/visitor"
9
9
  require "graphql_migrate_execution/strategy"
10
10
  require "graphql_migrate_execution/implicit"
11
11
  require "graphql_migrate_execution/do_nothing"
12
+ require "graphql_migrate_execution/fallback_value"
12
13
  require "graphql_migrate_execution/resolve_batch"
13
14
  require "graphql_migrate_execution/resolve_each"
14
15
  require "graphql_migrate_execution/resolve_static"
data/readme.md CHANGED
@@ -186,4 +186,8 @@ bundle exec rake test # TEST=test/...
186
186
 
187
187
  ## TODO
188
188
 
189
+ - [ ] Interfaces: use a `resolver_methods` block with instance methods
190
+ - [ ] `@object` is not migrated, only `object` is
191
+ - [ ] `**kwargs` is not correctly migrated into `resolve_static` methods
189
192
  - [ ] Does `--cleanup` work on my app? I haven't run it yet.
193
+ - [ ] Doesn't support `def ... =`-style single-line methods
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql_migrate_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-23 00:00:00.000000000 Z
10
+ date: 2026-05-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: irb
@@ -96,6 +96,7 @@ files:
96
96
  - lib/graphql_migrate_execution/dataloader_manual.rb
97
97
  - lib/graphql_migrate_execution/dataloader_shorthand.rb
98
98
  - lib/graphql_migrate_execution/do_nothing.rb
99
+ - lib/graphql_migrate_execution/fallback_value.rb
99
100
  - lib/graphql_migrate_execution/field_definition.rb
100
101
  - lib/graphql_migrate_execution/hash_key.rb
101
102
  - lib/graphql_migrate_execution/implicit.rb