graphql 1.8.0.pre11 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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