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 +4 -4
- data/lib/graphql_migrate_execution/dataloader_all.rb +9 -0
- data/lib/graphql_migrate_execution/fallback_value.rb +39 -0
- data/lib/graphql_migrate_execution/field_definition.rb +7 -2
- data/lib/graphql_migrate_execution/migration.rb +5 -1
- data/lib/graphql_migrate_execution/resolve_static.rb +1 -0
- data/lib/graphql_migrate_execution/resolver_method.rb +1 -1
- data/lib/graphql_migrate_execution/strategy.rb +51 -13
- data/lib/graphql_migrate_execution/type_definition.rb +2 -1
- data/lib/graphql_migrate_execution/version.rb +1 -1
- data/lib/graphql_migrate_execution/visitor.rb +24 -10
- data/lib/graphql_migrate_execution.rb +1 -0
- data/readme.md +4 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c879a6e266b7520abe76bf69a63cbc7ff8a446fe6438ad0ca37c89368f4c153
|
|
4
|
+
data.tar.gz: cb33f65e293a55a01d3d2e440eb23f40e2f5c3fc28f1241720f40cacf76ca5d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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!(
|
|
116
|
+
new_class_method.sub!(new_method_name, "#{new_method_name}(#{new_params})")
|
|
95
117
|
else
|
|
96
|
-
new_class_method.sub!("
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
|
@@ -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.
|
|
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
|
|
129
|
-
|
|
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.
|
|
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-
|
|
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
|