graphql 1.8.0.pre2 → 1.8.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -1
  3. data/lib/graphql/deprecated_dsl.rb +2 -0
  4. data/lib/graphql/enum_type.rb +1 -1
  5. data/lib/graphql/field.rb +10 -1
  6. data/lib/graphql/input_object_type.rb +3 -1
  7. data/lib/graphql/introspection.rb +3 -10
  8. data/lib/graphql/introspection/base_object.rb +15 -0
  9. data/lib/graphql/introspection/directive_location_enum.rb +11 -7
  10. data/lib/graphql/introspection/directive_type.rb +23 -16
  11. data/lib/graphql/introspection/dynamic_fields.rb +11 -0
  12. data/lib/graphql/introspection/entry_points.rb +29 -0
  13. data/lib/graphql/introspection/enum_value_type.rb +16 -11
  14. data/lib/graphql/introspection/field_type.rb +21 -12
  15. data/lib/graphql/introspection/input_value_type.rb +26 -23
  16. data/lib/graphql/introspection/schema_field.rb +7 -2
  17. data/lib/graphql/introspection/schema_type.rb +36 -22
  18. data/lib/graphql/introspection/type_by_name_field.rb +10 -2
  19. data/lib/graphql/introspection/type_kind_enum.rb +10 -6
  20. data/lib/graphql/introspection/type_type.rb +85 -23
  21. data/lib/graphql/introspection/typename_field.rb +1 -0
  22. data/lib/graphql/language.rb +1 -0
  23. data/lib/graphql/language/document_from_schema_definition.rb +129 -37
  24. data/lib/graphql/language/generation.rb +3 -182
  25. data/lib/graphql/language/nodes.rb +12 -2
  26. data/lib/graphql/language/parser.rb +63 -55
  27. data/lib/graphql/language/parser.y +2 -1
  28. data/lib/graphql/language/printer.rb +351 -0
  29. data/lib/graphql/object_type.rb +1 -1
  30. data/lib/graphql/query.rb +1 -1
  31. data/lib/graphql/query/arguments.rb +24 -8
  32. data/lib/graphql/query/context.rb +3 -0
  33. data/lib/graphql/query/literal_input.rb +4 -1
  34. data/lib/graphql/railtie.rb +28 -6
  35. data/lib/graphql/schema.rb +42 -7
  36. data/lib/graphql/schema/enum.rb +1 -0
  37. data/lib/graphql/schema/field.rb +26 -5
  38. data/lib/graphql/schema/field/dynamic_resolve.rb +18 -9
  39. data/lib/graphql/schema/input_object.rb +2 -2
  40. data/lib/graphql/schema/introspection_system.rb +93 -0
  41. data/lib/graphql/schema/late_bound_type.rb +32 -0
  42. data/lib/graphql/schema/member.rb +21 -1
  43. data/lib/graphql/schema/member/build_type.rb +8 -6
  44. data/lib/graphql/schema/member/has_fields.rb +1 -1
  45. data/lib/graphql/schema/member/instrumentation.rb +3 -1
  46. data/lib/graphql/schema/member/list_type_proxy.rb +4 -0
  47. data/lib/graphql/schema/member/non_null_type_proxy.rb +4 -0
  48. data/lib/graphql/schema/object.rb +2 -1
  49. data/lib/graphql/schema/printer.rb +33 -266
  50. data/lib/graphql/schema/traversal.rb +74 -6
  51. data/lib/graphql/schema/validation.rb +3 -2
  52. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +6 -6
  53. data/lib/graphql/tracing/scout_tracing.rb +2 -2
  54. data/lib/graphql/upgrader/member.rb +463 -63
  55. data/lib/graphql/version.rb +1 -1
  56. data/spec/fixtures/upgrader/blame_range.original.rb +43 -0
  57. data/spec/fixtures/upgrader/blame_range.transformed.rb +31 -0
  58. data/spec/fixtures/upgrader/subscribable.original.rb +51 -0
  59. data/spec/fixtures/upgrader/subscribable.transformed.rb +46 -0
  60. data/spec/fixtures/upgrader/type_x.original.rb +35 -0
  61. data/spec/fixtures/upgrader/type_x.transformed.rb +35 -0
  62. data/spec/graphql/language/document_from_schema_definition_spec.rb +729 -296
  63. data/spec/graphql/language/generation_spec.rb +21 -186
  64. data/spec/graphql/language/nodes_spec.rb +21 -0
  65. data/spec/graphql/language/printer_spec.rb +203 -0
  66. data/spec/graphql/query/arguments_spec.rb +14 -4
  67. data/spec/graphql/query/context_spec.rb +17 -0
  68. data/spec/graphql/schema/build_from_definition_spec.rb +13 -4
  69. data/spec/graphql/schema/field_spec.rb +14 -0
  70. data/spec/graphql/schema/introspection_system_spec.rb +39 -0
  71. data/spec/graphql/schema/object_spec.rb +12 -1
  72. data/spec/graphql/schema/printer_spec.rb +14 -14
  73. data/spec/graphql/tracing/platform_tracing_spec.rb +2 -2
  74. data/spec/graphql/upgrader/member_spec.rb +274 -62
  75. data/spec/support/jazz.rb +75 -3
  76. metadata +38 -9
  77. data/lib/graphql/introspection/arguments_field.rb +0 -7
  78. data/lib/graphql/introspection/enum_values_field.rb +0 -18
  79. data/lib/graphql/introspection/fields_field.rb +0 -13
  80. data/lib/graphql/introspection/input_fields_field.rb +0 -12
  81. data/lib/graphql/introspection/interfaces_field.rb +0 -11
  82. data/lib/graphql/introspection/of_type_field.rb +0 -6
  83. data/lib/graphql/introspection/possible_types_field.rb +0 -11
@@ -32,7 +32,7 @@ module GraphQL
32
32
  # @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
33
33
 
34
34
  # @!attribute mutation
35
- # @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation
35
+ # @return [GraphQL::Relay::Mutation, nil] The mutation this object type was derived from, if it is an auto-generated payload type.
36
36
 
37
37
  def initialize
38
38
  super
@@ -79,7 +79,7 @@ module GraphQL
79
79
  def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: {}, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil, except: nil, only: nil)
80
80
  @schema = schema
81
81
  @filter = schema.default_filter.merge(except: except, only: only)
82
- @context = Context.new(query: self, object: root_value, values: context)
82
+ @context = schema.context_class.new(query: self, object: root_value, values: context)
83
83
  @subscription_topic = subscription_topic
84
84
  @root_value = root_value
85
85
  @fragments = nil
@@ -35,13 +35,14 @@ module GraphQL
35
35
  end
36
36
  end
37
37
 
38
- def initialize(values, context:)
38
+ def initialize(values, context:, defaults_used:)
39
39
  @argument_values = values.inject({}) do |memo, (inner_key, inner_value)|
40
- arg_defn = self.class.argument_definitions[inner_key.to_s]
41
-
40
+ arg_name = inner_key.to_s
41
+ arg_defn = self.class.argument_definitions[arg_name]
42
+ arg_default_used = defaults_used.include?(arg_name)
42
43
  arg_value = wrap_value(inner_value, arg_defn.type, context)
43
44
  string_key = arg_defn.expose_as
44
- memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn)
45
+ memo[string_key] = ArgumentValue.new(string_key, arg_value, arg_defn, arg_default_used)
45
46
  memo
46
47
  end
47
48
  end
@@ -60,6 +61,13 @@ module GraphQL
60
61
  @argument_values.key?(key_s)
61
62
  end
62
63
 
64
+ # @param key [String, Symbol] name of value to access
65
+ # @return [Boolean] true if the argument default was passed as the argument value to the resolver
66
+ def default_used?(key)
67
+ key_s = key.is_a?(String) ? key : key.to_s
68
+ @argument_values.fetch(key_s, NULL_ARGUMENT_VALUE).default_used?
69
+ end
70
+
63
71
  # Get the hash of all values, with stringified keys
64
72
  # @return [Hash] the stringified hash
65
73
  def to_h
@@ -92,20 +100,28 @@ module GraphQL
92
100
  self.argument_definitions = []
93
101
  end
94
102
 
95
- NO_ARGS = NoArguments.new({}, context: nil)
103
+ NO_ARGS = NoArguments.new({}, context: nil, defaults_used: Set.new)
96
104
 
97
105
  private
98
106
 
99
107
  class ArgumentValue
100
108
  attr_reader :key, :value, :definition
101
- def initialize(key, value, definition)
109
+ attr_writer :default_used
110
+
111
+ def initialize(key, value, definition, default_used)
102
112
  @key = key
103
113
  @value = value
104
114
  @definition = definition
115
+ @default_used = default_used
116
+ end
117
+
118
+ # @return [Boolean] true if the argument default was passed as the argument value to the resolver
119
+ def default_used?
120
+ @default_used
105
121
  end
106
122
  end
107
123
 
108
- NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil)
124
+ NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil, nil)
109
125
 
110
126
  def wrap_value(value, arg_defn_type, context)
111
127
  if value.nil?
@@ -118,7 +134,7 @@ module GraphQL
118
134
  wrap_value(value, arg_defn_type.of_type, context)
119
135
  when GraphQL::InputObjectType
120
136
  if value.is_a?(Hash)
121
- arg_defn_type.arguments_class.new(value, context: context)
137
+ arg_defn_type.arguments_class.new(value, context: context, defaults_used: Set.new)
122
138
  else
123
139
  value
124
140
  end
@@ -268,3 +268,6 @@ module GraphQL
268
268
  end
269
269
  end
270
270
  end
271
+
272
+
273
+ GraphQL::Schema::Context = GraphQL::Query::Context
@@ -49,6 +49,8 @@ module GraphQL
49
49
  def self.from_arguments(ast_arguments, argument_owner, variables)
50
50
  context = variables ? variables.context : nil
51
51
  values_hash = {}
52
+ defaults_used = Set.new
53
+
52
54
  indexed_arguments = case ast_arguments
53
55
  when Hash
54
56
  ast_arguments
@@ -93,6 +95,7 @@ module GraphQL
93
95
  # then add the default value.
94
96
  if arg_defn.default_value? && !values_hash.key?(arg_name)
95
97
  value = arg_defn.default_value
98
+ defaults_used << arg_name
96
99
  # `context` isn't present when pre-calculating defaults
97
100
  if context
98
101
  value = arg_defn.prepare(value, context)
@@ -105,7 +108,7 @@ module GraphQL
105
108
  end
106
109
  end
107
110
 
108
- argument_owner.arguments_class.new(values_hash, context: context)
111
+ argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used)
109
112
  end
110
113
  end
111
114
  end
@@ -19,17 +19,40 @@ module GraphQL
19
19
  Rake::Task["graphql:upgrade:member"].execute(Struct.new(:member_file).new(file))
20
20
  end
21
21
  end
22
+
23
+ puts "Upgrade complete! Note that this is a best-effort approach, and may very well contain some bugs."
24
+ puts "Don't forget to create the base objects. For example, you could run:"
25
+ puts "\tbin/rake graphql:upgrade:create_base_objects[app/graphql]"
22
26
  end
23
27
 
24
28
  namespace :upgrade do
25
29
  task :create_base_objects, [:base_dir] do |t, args|
26
- base_dir = args.base_dir
30
+ unless (base_dir = args[:base_dir])
31
+ fail 'You have to give me a directory where your GraphQL types live. ' \
32
+ 'For example: `bin/rake graphql:upgrade:create_base_objects[app/graphql]`'
33
+ end
34
+
35
+ destination_file = File.join(base_dir, "types", "base_scalar.rb")
36
+ unless File.exists?(destination_file)
37
+ FileUtils.mkdir_p(File.dirname(destination_file))
38
+ File.open(destination_file, 'w') do |f|
39
+ f.write "class Types::BaseScalar < GraphQL::Schema::Scalar\nend"
40
+ end
41
+ end
42
+
43
+ destination_file = File.join(base_dir, "types", "base_input_object.rb")
44
+ unless File.exists?(destination_file)
45
+ FileUtils.mkdir_p(File.dirname(destination_file))
46
+ File.open(destination_file, 'w') do |f|
47
+ f.write "class Types::BaseInputObject < GraphQL::Schema::InputObject\nend"
48
+ end
49
+ end
27
50
 
28
51
  destination_file = File.join(base_dir, "types", "base_enum.rb")
29
52
  unless File.exists?(destination_file)
30
53
  FileUtils.mkdir_p(File.dirname(destination_file))
31
54
  File.open(destination_file, 'w') do |f|
32
- f.write "class Types::BaseEnum < GraphQL::Schema::Enum; end"
55
+ f.write "class Types::BaseEnum < GraphQL::Schema::Enum\nend"
33
56
  end
34
57
  end
35
58
 
@@ -37,7 +60,7 @@ module GraphQL
37
60
  unless File.exists?(destination_file)
38
61
  FileUtils.mkdir_p(File.dirname(destination_file))
39
62
  File.open(destination_file, 'w') do |f|
40
- f.write "class Types::BaseUnion < GraphQL::Schema::Union; end"
63
+ f.write "class Types::BaseUnion < GraphQL::Schema::Union\nend"
41
64
  end
42
65
  end
43
66
 
@@ -45,14 +68,14 @@ module GraphQL
45
68
  unless File.exists?(destination_file)
46
69
  FileUtils.mkdir_p(File.dirname(destination_file))
47
70
  File.open(destination_file, 'w') do |f|
48
- f.write "class Types::BaseInterface < GraphQL::Schema::Interface; end"
71
+ f.write "class Types::BaseInterface < GraphQL::Schema::Interface\nend"
49
72
  end
50
73
  end
51
74
 
52
75
  destination_file = File.join(base_dir, "types", "base_object.rb")
53
76
  unless File.exists?(destination_file)
54
77
  File.open(destination_file, 'w') do |f|
55
- f.write "class Types::BaseObject < GraphQL::Schema::Object; end"
78
+ f.write "class Types::BaseObject < GraphQL::Schema::Object\nend"
56
79
  end
57
80
  end
58
81
  end
@@ -80,4 +103,3 @@ module GraphQL
80
103
  end
81
104
  end
82
105
  end
83
-
@@ -4,6 +4,8 @@ require "graphql/schema/catchall_middleware"
4
4
  require "graphql/schema/default_parse_error"
5
5
  require "graphql/schema/default_type_error"
6
6
  require "graphql/schema/invalid_type_error"
7
+ require "graphql/schema/introspection_system"
8
+ require "graphql/schema/late_bound_type"
7
9
  require "graphql/schema/middleware_chain"
8
10
  require "graphql/schema/null_mask"
9
11
  require "graphql/schema/possible_types"
@@ -91,7 +93,7 @@ module GraphQL
91
93
  :orphan_types, :directives,
92
94
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
93
95
  :cursor_encoder,
94
- :raise_definition_error
96
+ :raise_definition_error, :introspection_namespace
95
97
 
96
98
  # Single, long-lived instance of the provided subscriptions class, if there is one.
97
99
  # @return [GraphQL::Subscriptions]
@@ -104,6 +106,10 @@ module GraphQL
104
106
  # @see {Query.new} for query-specific filters with `except:`
105
107
  attr_accessor :default_mask
106
108
 
109
+ # @see {GraphQL::Query::Context} The parent class of these classes
110
+ # @return [Class] Instantiated for each query
111
+ attr_accessor :context_class
112
+
107
113
  class << self
108
114
  attr_accessor :default_execution_strategy
109
115
  end
@@ -148,6 +154,9 @@ module GraphQL
148
154
  @subscription_execution_strategy = self.class.default_execution_strategy
149
155
  @default_mask = GraphQL::Schema::NullMask
150
156
  @rebuilding_artifacts = false
157
+ @context_class = GraphQL::Query::Context
158
+ @introspection_namespace = nil
159
+ @introspection_system = nil
151
160
  end
152
161
 
153
162
  def initialize_copy(other)
@@ -175,6 +184,7 @@ module GraphQL
175
184
  # This will be rebuilt when it's requested
176
185
  # or during a later `define` call
177
186
  @types = nil
187
+ @introspection_system = nil
178
188
  end
179
189
 
180
190
  def rescue_from(*args, &block)
@@ -250,6 +260,14 @@ module GraphQL
250
260
  end
251
261
  end
252
262
 
263
+ # @api private
264
+ def introspection_system
265
+ @introspection_system ||= begin
266
+ rebuild_artifacts
267
+ @introspection_system
268
+ end
269
+ end
270
+
253
271
  # Returns a list of Arguments and Fields referencing a certain type
254
272
  # @param type_name [String]
255
273
  # @return [Hash]
@@ -331,12 +349,10 @@ module GraphQL
331
349
  defined_field = @instrumented_field_map[parent_type_name][field_name]
332
350
  if defined_field
333
351
  defined_field
334
- elsif field_name == "__typename"
335
- GraphQL::Introspection::TypenameField
336
- elsif field_name == "__schema" && parent_type == query
337
- GraphQL::Introspection::SchemaField
338
- elsif field_name == "__type" && parent_type == query
339
- GraphQL::Introspection::TypeByNameField
352
+ elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
353
+ entry_point_field
354
+ elsif (dynamic_field = introspection_system.dynamic_field(name: field_name))
355
+ dynamic_field
340
356
  else
341
357
  nil
342
358
  end
@@ -632,9 +648,11 @@ module GraphQL
632
648
  directives(DIRECTIVES)
633
649
  end
634
650
  schema_defn.directives = directives
651
+ schema_defn.introspection_namespace = introspection
635
652
  schema_defn.resolve_type = method(:resolve_type)
636
653
  schema_defn.object_from_id = method(:object_from_id)
637
654
  schema_defn.id_from_object = method(:id_from_object)
655
+ schema_defn.context_class = context_class
638
656
  instrumenters.each do |step, insts|
639
657
  insts.each do |inst|
640
658
  schema_defn.instrumenters[step] << inst
@@ -686,6 +704,14 @@ module GraphQL
686
704
  end
687
705
  end
688
706
 
707
+ def introspection(new_introspection_namespace = nil)
708
+ if new_introspection_namespace
709
+ @introspection = new_introspection_namespace
710
+ else
711
+ @introspection
712
+ end
713
+ end
714
+
689
715
  def default_max_page_size(new_default_max_page_size = nil)
690
716
  if new_default_max_page_size
691
717
  @default_max_page_size = new_default_max_page_size
@@ -718,6 +744,14 @@ module GraphQL
718
744
  end
719
745
  end
720
746
 
747
+ def context_class(new_context_class = nil)
748
+ if new_context_class
749
+ @context_class = new_context_class
750
+ else
751
+ @context_class || GraphQL::Query::Context
752
+ end
753
+ end
754
+
721
755
  def resolve_type(type, obj, ctx)
722
756
  raise NotImplementedError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types or Interface types (tried to resolve: #{type.name})"
723
757
  end
@@ -805,6 +839,7 @@ module GraphQL
805
839
  raise CyclicalDefinitionError, "Part of the schema build process re-triggered the schema build process, causing an infinite loop. Avoid using Schema#types, Schema#possible_types, and Schema#get_field during schema build."
806
840
  else
807
841
  @rebuilding_artifacts = true
842
+ @introspection_system = Schema::IntrospectionSystem.new(self)
808
843
  traversal = Traversal.new(self)
809
844
  @types = traversal.type_map
810
845
  @root_types = [query, mutation, subscription]
@@ -54,6 +54,7 @@ module GraphQL
54
54
  enum_type = GraphQL::EnumType.new
55
55
  enum_type.name = graphql_name
56
56
  enum_type.description = description
57
+ enum_type.introspection = introspection
57
58
  values.each do |val|
58
59
  enum_value = GraphQL::EnumType::EnumValue.new
59
60
  enum_value.name = val.name
@@ -13,7 +13,7 @@ module GraphQL
13
13
  # @return [String]
14
14
  attr_reader :description
15
15
 
16
- def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, &args_block)
16
+ def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &args_block)
17
17
  if !(field || function)
18
18
  if return_type_expr.nil?
19
19
  raise ArgumentError, "missing positional argument `type`"
@@ -22,8 +22,14 @@ module GraphQL
22
22
  raise ArgumentError, "missing keyword argument null:"
23
23
  end
24
24
  end
25
+ if (field || function || resolve) && extras.any?
26
+ raise ArgumentError, "keyword `extras:` may only be used with method-based resolve, please remove `field:`, `function:`, or `resolve:`"
27
+ end
25
28
  @name = name.to_s
26
- @description = desc
29
+ if description && desc
30
+ raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{description.inspect})"
31
+ end
32
+ @description = description || desc
27
33
  @field = field
28
34
  @function = function
29
35
  @resolve = resolve
@@ -34,6 +40,8 @@ module GraphQL
34
40
  @args_block = args_block
35
41
  @connection = connection
36
42
  @max_page_size = max_page_size
43
+ @introspection = introspection
44
+ @extras = extras
37
45
  end
38
46
 
39
47
  # @return [GraphQL::Field]
@@ -49,7 +57,6 @@ module GraphQL
49
57
  end
50
58
 
51
59
  field_defn.name = Member::BuildType.camelize(name)
52
-
53
60
  if @return_type_expr
54
61
  return_type_name = Member::BuildType.to_type_name(@return_type_expr)
55
62
  connection = @connection.nil? ? return_type_name.end_with?("Connection") : @connection
@@ -75,11 +82,16 @@ module GraphQL
75
82
  prev_resolve = @resolve || field_defn.resolve_proc
76
83
  UnwrappedResolve.new(inner_resolve: prev_resolve)
77
84
  else
78
- DynamicResolve.new(method_name: method_name, connection: connection)
85
+ DynamicResolve.new(
86
+ method_name: method_name,
87
+ connection: connection,
88
+ extras: @extras
89
+ )
79
90
  end
80
91
 
81
92
  field_defn.connection = connection
82
93
  field_defn.connection_max_page_size = @max_page_size
94
+ field_defn.introspection = @introspection
83
95
 
84
96
  field_proxy = FieldProxy.new(field_defn, argument_class: self.class.argument_class)
85
97
  # apply this first, so it can be overriden below
@@ -122,7 +134,16 @@ module GraphQL
122
134
  # This is the `argument(...)` DSL for class-based field definitons
123
135
  def argument(*args)
124
136
  arg = @argument_class.new(*args)
125
- @field.arguments[arg.name] = arg.graphql_definition
137
+ graphql_arg = arg.graphql_definition
138
+ @field.arguments[graphql_arg.name] = graphql_arg
139
+ end
140
+
141
+ def description(text)
142
+ if @field.description
143
+ fail "You're overriding the description of #{@field.name} in the provided block!"
144
+ else
145
+ @field.description = text
146
+ end
126
147
  end
127
148
  end
128
149
  end
@@ -4,16 +4,17 @@ module GraphQL
4
4
  class Schema
5
5
  class Field
6
6
  class DynamicResolve
7
- def initialize(method_name:, connection:)
7
+ def initialize(method_name:, connection:, extras:)
8
8
  @method_name = method_name
9
9
  @connection = connection
10
+ @extras = extras
10
11
  end
11
12
 
12
13
  def call(obj, args, ctx)
13
14
  if obj.respond_to?(@method_name)
14
- public_send_field(obj, @method_name, args)
15
+ public_send_field(obj, @method_name, args, ctx)
15
16
  elsif obj.object.respond_to?(@method_name)
16
- public_send_field(obj.object, @method_name, args)
17
+ public_send_field(obj.object, @method_name, args, ctx)
17
18
  elsif obj.is_a?(Hash)
18
19
  obj[@method_name]
19
20
  else
@@ -31,8 +32,10 @@ ERR
31
32
 
32
33
  private
33
34
 
34
- def public_send_field(obj, method_name, graphql_args)
35
- if graphql_args.any?
35
+ NO_ARGS = {}.freeze
36
+
37
+ def public_send_field(obj, method_name, graphql_args, field_ctx)
38
+ if graphql_args.any? || @extras.any?
36
39
  # Splat the GraphQL::Arguments to Ruby keyword arguments
37
40
  ruby_kwargs = {}
38
41
 
@@ -48,11 +51,17 @@ ERR
48
51
  ruby_kwargs.delete(:after)
49
52
  end
50
53
 
51
- if ruby_kwargs.any?
52
- obj.public_send(method_name, ruby_kwargs)
53
- else
54
- obj.public_send(method_name)
54
+ @extras.each do |extra_arg|
55
+ # TODO: provide proper tests for `:ast_node`, `:irep_node`, `:parent`, others?
56
+ ruby_kwargs[extra_arg] = field_ctx.public_send(extra_arg)
55
57
  end
58
+ else
59
+ ruby_kwargs = NO_ARGS
60
+ end
61
+
62
+
63
+ if ruby_kwargs.any?
64
+ obj.public_send(method_name, ruby_kwargs)
56
65
  else
57
66
  obj.public_send(method_name)
58
67
  end