graphql 1.8.0.pre2 → 1.8.0.pre3

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 (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