graphql 1.8.0.pre11 → 1.8.0

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +1 -1
  3. data/lib/graphql/function.rb +2 -0
  4. data/lib/graphql/railtie.rb +1 -1
  5. data/lib/graphql/schema.rb +1 -0
  6. data/lib/graphql/schema/argument.rb +3 -2
  7. data/lib/graphql/schema/build_from_definition.rb +1 -1
  8. data/lib/graphql/schema/field.rb +96 -49
  9. data/lib/graphql/schema/interface.rb +21 -3
  10. data/lib/graphql/schema/list.rb +4 -0
  11. data/lib/graphql/schema/member/accepts_definition.rb +2 -2
  12. data/lib/graphql/schema/member/base_dsl_methods.rb +4 -0
  13. data/lib/graphql/schema/member/build_type.rb +4 -2
  14. data/lib/graphql/schema/member/has_fields.rb +1 -8
  15. data/lib/graphql/schema/mutation.rb +19 -88
  16. data/lib/graphql/schema/non_null.rb +4 -0
  17. data/lib/graphql/schema/object.rb +1 -1
  18. data/lib/graphql/schema/relay_classic_mutation.rb +14 -15
  19. data/lib/graphql/schema/resolver.rb +122 -0
  20. data/lib/graphql/subscriptions/instrumentation.rb +5 -1
  21. data/lib/graphql/subscriptions/serialize.rb +2 -0
  22. data/lib/graphql/tracing/new_relic_tracing.rb +26 -0
  23. data/lib/graphql/version.rb +1 -1
  24. data/readme.md +1 -1
  25. data/spec/generators/graphql/install_generator_spec.rb +1 -1
  26. data/spec/graphql/relay/mutation_spec.rb +5 -3
  27. data/spec/graphql/schema/build_from_definition_spec.rb +1 -1
  28. data/spec/graphql/schema/field_spec.rb +7 -24
  29. data/spec/graphql/schema/interface_spec.rb +25 -0
  30. data/spec/graphql/schema/member/accepts_definition_spec.rb +22 -0
  31. data/spec/graphql/schema/member/build_type_spec.rb +17 -0
  32. data/spec/graphql/schema/mutation_spec.rb +15 -14
  33. data/spec/graphql/schema/resolver_spec.rb +131 -0
  34. data/spec/graphql/subscriptions_spec.rb +267 -205
  35. data/spec/graphql/tracing/new_relic_tracing_spec.rb +47 -0
  36. data/spec/support/jazz.rb +6 -1
  37. data/spec/support/new_relic.rb +24 -0
  38. data/spec/support/star_trek/schema.rb +2 -2
  39. data/spec/support/star_wars/schema.rb +1 -2
  40. metadata +13 -4
@@ -29,6 +29,10 @@ module GraphQL
29
29
  def kind
30
30
  GraphQL::TypeKinds::NON_NULL
31
31
  end
32
+
33
+ def unwrap
34
+ @of_type.unwrap
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -45,7 +45,7 @@ module GraphQL
45
45
  if int.is_a?(GraphQL::InterfaceType)
46
46
  int_f = {}
47
47
  int.fields.each do |name, legacy_field|
48
- int_f[name] = build_field(name, field: legacy_field)
48
+ int_f[name] = field_class.from_options(name, field: legacy_field)
49
49
  end
50
50
  all_fields = int_f.merge(all_fields)
51
51
  end
@@ -22,22 +22,20 @@ module GraphQL
22
22
  class RelayClassicMutation < GraphQL::Schema::Mutation
23
23
  # The payload should always include this field
24
24
  field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", null: true)
25
-
25
+ # Relay classic default:
26
+ null(true)
26
27
 
27
28
  # Override {GraphQL::Schema::Mutation#resolve_mutation} to
28
29
  # delete `client_mutation_id` from the kwargs.
29
30
  def resolve_mutation(kwargs)
30
31
  # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
31
32
  kwargs.delete(:client_mutation_id)
32
- super
33
+ resolve(**kwargs)
33
34
  end
34
35
 
35
- class << self
36
- def inherited(base)
37
- base.null(true)
38
- super
39
- end
36
+ resolve_method(:resolve_mutation)
40
37
 
38
+ class << self
41
39
  # The base class for generated input object types
42
40
  # @param new_class [Class] The base class to use for generating input object definitions
43
41
  # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
@@ -57,16 +55,17 @@ module GraphQL
57
55
  @input_type ||= generate_input_type
58
56
  end
59
57
 
60
- private
61
-
62
- # Extend {Schema::Mutation.generate_field} to add the `input` argument
63
- def generate_field
64
- field_instance = super
65
- field_instance.own_arguments.clear
66
- field_instance.argument(:input, input_type, required: true)
67
- field_instance
58
+ # Extend {Schema::Mutation.field_options} to add the `input` argument
59
+ def field_options
60
+ sig = super
61
+ # Arguments were added at the root, but they should be nested
62
+ sig[:arguments].clear
63
+ sig[:arguments][:input] = { type: input_type, required: true }
64
+ sig
68
65
  end
69
66
 
67
+ private
68
+
70
69
  # Generate the input type for the `input:` argument
71
70
  # To customize how input objects are generated, override this method
72
71
  # @return [Class] a subclass of {.input_object_class}
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # A class-based container for field configuration and resolution logic. It supports:
6
+ #
7
+ # - Arguments, via `.argument(...)` helper, which will be applied to the field.
8
+ # - Return type, via `.type(..., null: ...)`, which will be applied to the field.
9
+ # - Description, via `.description(...)`, which will be applied to the field
10
+ # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
11
+ # - `#object` and `#context` accessors for use during `#resolve`.
12
+ #
13
+ # Resolvers can be attached with the `resolver:` option in a `field(...)` call.
14
+ #
15
+ # A resolver's configuration may be overridden with other keywords in the `field(...)` call.
16
+ #
17
+ # See the {.field_options} to see how a Resolver becomes a set of field configuration options.
18
+ #
19
+ # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`.
20
+ # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
21
+ class Resolver
22
+ include Schema::Member::GraphQLTypeNames
23
+ # Really we only need description from here, but:
24
+ extend Schema::Member::BaseDSLMethods
25
+ extend GraphQL::Schema::Member::HasArguments
26
+
27
+ # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
28
+ # @param context [GraphQL::Query::Context]
29
+ def initialize(object:, context:)
30
+ @object = object
31
+ @context = context
32
+ end
33
+
34
+ # @return [Object] The application object this field is being resolved on
35
+ attr_reader :object
36
+
37
+ # @return [GraphQL::Query::Context]
38
+ attr_reader :context
39
+
40
+ # Do the work. Everything happens here.
41
+ # @return [Object] An object corresponding to the return type
42
+ def resolve(**args)
43
+ raise NotImplementedError, "#{self.class.name}#resolve should execute the field's logic"
44
+ end
45
+
46
+ class << self
47
+ # Default `:resolve` set below.
48
+ # @return [Symbol] The method to call on instances of this object to resolve the field
49
+ def resolve_method(new_method = nil)
50
+ if new_method
51
+ @resolve_method = new_method
52
+ end
53
+ @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve)
54
+ end
55
+
56
+ # Additional info injected into {#resolve}
57
+ # @see {GraphQL::Schema::Field#extras}
58
+ def extras(new_extras = nil)
59
+ if new_extras
60
+ @own_extras = new_extras
61
+ end
62
+ own_extras = @own_extras || []
63
+ own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
64
+ end
65
+
66
+ # Specifies whether or not the field is nullable. Defaults to `true`
67
+ # TODO unify with {#type}
68
+ # @param allow_null [Boolean] Whether or not the response can be null
69
+ def null(allow_null = nil)
70
+ if !allow_null.nil?
71
+ @null = allow_null
72
+ end
73
+
74
+ @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null
75
+ end
76
+
77
+ # Call this method to get the return type of the field,
78
+ # or use it as a configuration method to assign a return type
79
+ # instead of generating one.
80
+ # TODO unify with {#null}
81
+ # @param new_type [Class, nil] If a type definition class is provided, it will be used as the return type of the field
82
+ # @param null [true, false] Whether or not the field may return `nil`
83
+ # @return [Class] The type which this field returns.
84
+ def type(new_type = nil, null: nil)
85
+ if new_type
86
+ if null.nil?
87
+ raise ArgumentError, "required argument `null:` is missing"
88
+ end
89
+ @type_expr = new_type
90
+ @null = null
91
+ else
92
+ if @type_expr
93
+ GraphQL::Schema::Member::BuildType.parse_type(@type_expr, null: @null)
94
+ elsif superclass.respond_to?(:type)
95
+ superclass.type
96
+ else
97
+ nil
98
+ end
99
+ end
100
+ end
101
+
102
+ def field_options
103
+ {
104
+ type: type_expr,
105
+ description: description,
106
+ extras: extras,
107
+ method: resolve_method,
108
+ resolver_class: self,
109
+ arguments: arguments,
110
+ null: null,
111
+ }
112
+ end
113
+
114
+ # A non-normalized type configuration, without `null` applied
115
+ # @api private
116
+ def type_expr
117
+ @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -56,7 +56,11 @@ module GraphQL
56
56
  ctx.skip
57
57
  elsif ctx.irep_node.subscription_topic == ctx.query.subscription_topic
58
58
  # The root object is _already_ the subscription update:
59
- obj
59
+ if obj.is_a?(GraphQL::Schema::Object)
60
+ obj.object
61
+ else
62
+ obj
63
+ end
60
64
  else
61
65
  # This is a subscription update, but this event wasn't triggered.
62
66
  ctx.skip
@@ -35,6 +35,8 @@ module GraphQL
35
35
  obj.map { |i| dump_recursive(i) }.join(':')
36
36
  when obj.is_a?(Hash)
37
37
  obj.map { |k, v| "#{dump_recursive(k)}:#{dump_recursive(v)}" }.join(":")
38
+ when obj.is_a?(GraphQL::Schema::InputObject)
39
+ dump_recursive(obj.to_h)
38
40
  when obj.respond_to?(:to_gid_param)
39
41
  obj.to_gid_param
40
42
  when obj.respond_to?(:to_param)
@@ -14,7 +14,33 @@ module GraphQL
14
14
  "execute_query_lazy" => "GraphQL/execute",
15
15
  }
16
16
 
17
+ # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
18
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
+ # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
20
+ def initialize(set_transaction_name: false)
21
+ @set_transaction_name = set_transaction_name
22
+ super
23
+ end
24
+
17
25
  def platform_trace(platform_key, key, data)
26
+ if key == "execute_query"
27
+ set_this_txn_name = data[:query].context[:set_new_relic_transaction_name]
28
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
29
+ query = data[:query]
30
+ # Set the transaction name based on the operation type and name
31
+ selected_op = query.selected_operation
32
+ if selected_op
33
+ op_type = selected_op.operation_type
34
+ op_name = selected_op.name || "anonymous"
35
+ else
36
+ op_type = "query"
37
+ op_name = "anonymous"
38
+ end
39
+
40
+ NewRelic::Agent.set_transaction_name("GraphQL/#{op_type}.#{op_name}")
41
+ end
42
+ end
43
+
18
44
  NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
19
45
  yield
20
46
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre11"
3
+ VERSION = "1.8.0"
4
4
  end
data/readme.md CHANGED
@@ -37,7 +37,7 @@ Or, see ["Getting Started"](https://rmosolgo.github.io/graphql-ruby/).
37
37
 
38
38
  ## Upgrade
39
39
 
40
- I also sell [GraphQL::Pro](http://graphql.pro) which provides several features on top of the GraphQL runtime, including [authorization](http://rmosolgo.github.io/graphql-ruby/pro/authorization), [monitoring](http://rmosolgo.github.io/graphql-ruby/pro/monitoring) plugins and [persisted queries](http://rmosolgo.github.io/graphql-ruby/operation_store/overview). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development!
40
+ I also sell [GraphQL::Pro](http://graphql.pro) which provides several features on top of the GraphQL runtime, including [authorization](http://rmosolgo.github.io/graphql-ruby/pro/authorization), [Pusher-based subscriptions](http://graphql-ruby.org/subscriptions/pusher_implementation) and [persisted queries](http://rmosolgo.github.io/graphql-ruby/operation_store/overview). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development!
41
41
 
42
42
  ## Goals
43
43
 
@@ -192,7 +192,7 @@ DummySchema = GraphQL::Schema.define do
192
192
  }
193
193
 
194
194
  # Object Resolution
195
- resolve_type -> (obj, ctx) {
195
+ resolve_type -> (type, obj, ctx) {
196
196
  # TODO: Implement this function
197
197
  # to return the correct type for `obj`
198
198
  raise(NotImplementedError)
@@ -103,12 +103,14 @@ describe GraphQL::Relay::Mutation do
103
103
  end
104
104
 
105
105
  it "applies the description to the derived field" do
106
- assert_equal "Add a ship to this faction", StarWars::IntroduceShipMutation.field.description
106
+ field = GraphQL::Schema::Field.from_options(name: "x", **StarWars::IntroduceShipMutation.field_options)
107
+ assert_equal "Add a ship to this faction", field.description
107
108
  end
108
109
 
109
110
  it "inserts itself into the derived objects' metadata" do
110
- assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.field.mutation
111
- assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.graphql_field.mutation
111
+ field = GraphQL::Schema::Field.from_options(name: "x", **StarWars::IntroduceShipMutation.field_options)
112
+ assert_equal StarWars::IntroduceShipMutation, field.mutation
113
+ assert_equal StarWars::IntroduceShipMutation, field.to_graphql.mutation
112
114
  assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.payload_type.mutation
113
115
  assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.input_type.mutation
114
116
  end
@@ -668,7 +668,7 @@ type Type implements Interface {
668
668
  assert_equal [18, 1], schema.types["Union"].ast_node.position
669
669
  assert_equal [20, 1], schema.types["Scalar"].ast_node.position
670
670
  assert_equal [22, 1], schema.types["Input"].ast_node.position
671
- assert_equal [22, 1], schema.types["Input"].arguments["argument"].ast_node.position
671
+ assert_equal [23, 3], schema.types["Input"].arguments["argument"].ast_node.position
672
672
  assert_equal [26, 1], schema.directives["Directive"].ast_node.position
673
673
  assert_equal [28, 3], schema.directives["Directive"].arguments["argument"].ast_node.position
674
674
  assert_equal [31, 22], schema.types["Type"].ast_node.interfaces[0].position
@@ -19,7 +19,7 @@ describe GraphQL::Schema::Field do
19
19
  assert_equal 'inspectInput', field.graphql_definition.name
20
20
  assert_equal 'inspectInput', field.name
21
21
 
22
- underscored_field = GraphQL::Schema::Field.new(:underscored_field, String, null: false, camelize: false, owner: nil) do
22
+ underscored_field = GraphQL::Schema::Field.from_options(:underscored_field, String, null: false, camelize: false, owner: nil) do
23
23
  argument :underscored_arg, String, required: true, camelize: false
24
24
  end
25
25
 
@@ -55,7 +55,7 @@ describe GraphQL::Schema::Field do
55
55
  type = Class.new(GraphQL::Schema::Object) do
56
56
  graphql_name 'MyType'
57
57
  end
58
- field = GraphQL::Schema::Field.new(:my_field, type, owner: nil, null: true)
58
+ field = GraphQL::Schema::Field.from_options(:my_field, type, owner: nil, null: true)
59
59
  assert_equal type.to_graphql, field.to_graphql.type
60
60
  end
61
61
 
@@ -210,33 +210,16 @@ describe GraphQL::Schema::Field do
210
210
  end
211
211
 
212
212
  describe "mutation" do
213
- let(:mutation) do
214
- Class.new(GraphQL::Schema::Mutation) do
213
+ it "passes when not including extra arguments" do
214
+ mutation_class = Class.new(GraphQL::Schema::Mutation) do
215
215
  graphql_name "Thing"
216
-
217
216
  field :stuff, String, null: false
218
217
  end
219
- end
220
- let(:error_message) { "when keyword `mutation:` is present, all arguments are ignored, please remove them" }
221
218
 
222
- it "fails when including null option as true" do
223
- error = assert_raises(ArgumentError) do
224
- GraphQL::Schema::Field.new(:my_field, mutation: mutation, null: true)
219
+ obj = Class.new(GraphQL::Schema::Object) do
220
+ field(:my_field, mutation: mutation_class, null: true)
225
221
  end
226
-
227
- assert_equal error.message, error_message
228
- end
229
-
230
- it "fails when including null option as false" do
231
- error = assert_raises(ArgumentError) do
232
- GraphQL::Schema::Field.new(:my_field, mutation: mutation, null: false)
233
- end
234
-
235
- assert_equal error.message, error_message
236
- end
237
-
238
- it "passes when not including extra arguments" do
239
- assert_equal GraphQL::Schema::Field.new(:my_field, mutation: mutation).mutation, mutation
222
+ assert_equal obj.fields["myField"].mutation, mutation_class
240
223
  end
241
224
  end
242
225
  end
@@ -157,4 +157,29 @@ describe GraphQL::Schema::Interface do
157
157
  assert_equal(expected_data, res["data"]["find"])
158
158
  end
159
159
  end
160
+
161
+ describe ':DefinitionMethods' do
162
+ module InterfaceA
163
+ include GraphQL::Schema::Interface
164
+ end
165
+
166
+ module InterfaceB
167
+ include GraphQL::Schema::Interface
168
+ end
169
+
170
+ module InterfaceC
171
+ include GraphQL::Schema::Interface
172
+ end
173
+
174
+ it "doesn't overwrite them when including multiple interfaces" do
175
+ def_methods = InterfaceC::DefinitionMethods
176
+
177
+ InterfaceC.module_eval do
178
+ include InterfaceA
179
+ include InterfaceB
180
+ end
181
+
182
+ assert_equal(InterfaceC::DefinitionMethods, def_methods)
183
+ end
184
+ end
160
185
  end
@@ -46,6 +46,17 @@ describe GraphQL::Schema::Member::AcceptsDefinition do
46
46
  metadata2 :a, :bc
47
47
  end
48
48
 
49
+ module Thing2
50
+ include Thing
51
+ end
52
+
53
+ class SomeObject < BaseObject
54
+ metadata :a, :aaa
55
+ end
56
+
57
+ class SomeObject2 < SomeObject
58
+ end
59
+
49
60
  class Query < BaseObject
50
61
  metadata :a, :abc
51
62
  metadata2 :xyz, :zyx
@@ -56,6 +67,9 @@ describe GraphQL::Schema::Member::AcceptsDefinition do
56
67
  end
57
68
 
58
69
  field :thing, Thing, null: false
70
+ field :thing2, Thing2, null: false
71
+ field :some_object, SomeObject, null: false
72
+ field :some_object2, SomeObject2, null: false
59
73
  end
60
74
 
61
75
  query(Query)
@@ -71,6 +85,14 @@ describe GraphQL::Schema::Member::AcceptsDefinition do
71
85
  assert_equal [:z, 888], AcceptsDefinitionSchema::Thing.metadata
72
86
  assert_equal 888, AcceptsDefinitionSchema::Thing.graphql_definition.metadata[:z]
73
87
  assert_equal :bc, AcceptsDefinitionSchema::Thing.graphql_definition.metadata[:a]
88
+ # Interface inheritance
89
+ assert_equal [:z, 888], AcceptsDefinitionSchema::Thing2.metadata
90
+ assert_equal 888, AcceptsDefinitionSchema::Thing2.graphql_definition.metadata[:z]
91
+ assert_equal :bc, AcceptsDefinitionSchema::Thing2.graphql_definition.metadata[:a]
92
+
93
+ # Object inheritance
94
+ assert_equal :aaa, AcceptsDefinitionSchema::SomeObject.graphql_definition.metadata[:a]
95
+ assert_equal :aaa, AcceptsDefinitionSchema::SomeObject2.graphql_definition.metadata[:a]
74
96
  end
75
97
 
76
98
  it "passes along configs for fields and arguments" do
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Schema::Member::BuildType do
5
+ describe ".to_type_name" do
6
+ it "works with lists and non-nulls" do
7
+ t = Class.new(GraphQL::Schema::Object) do
8
+ graphql_name "T"
9
+ end
10
+
11
+ req_t = GraphQL::Schema::NonNull.new(t)
12
+ list_req_t = GraphQL::Schema::List.new(req_t)
13
+
14
+ assert_equal "T", GraphQL::Schema::Member::BuildType.to_type_name(list_req_t)
15
+ end
16
+ end
17
+ end