graphql 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/query.rb +7 -44
  3. data/lib/graphql/query/base_execution.rb +34 -0
  4. data/lib/graphql/query/base_execution/selected_object_resolution.rb +16 -0
  5. data/lib/graphql/query/directive_chain.rb +15 -4
  6. data/lib/graphql/query/executor.rb +53 -0
  7. data/lib/graphql/query/serial_execution.rb +12 -0
  8. data/lib/graphql/query/serial_execution/field_resolution.rb +42 -0
  9. data/lib/graphql/query/serial_execution/fragment_spread_resolution.rb +22 -0
  10. data/lib/graphql/query/serial_execution/inline_fragment_resolution.rb +21 -0
  11. data/lib/graphql/query/serial_execution/operation_resolution.rb +22 -0
  12. data/lib/graphql/query/serial_execution/selection_resolution.rb +42 -0
  13. data/lib/graphql/query/value_resolution.rb +76 -0
  14. data/lib/graphql/schema/type_validator.rb +2 -2
  15. data/lib/graphql/version.rb +1 -1
  16. data/readme.md +18 -12
  17. data/spec/graphql/directive_spec.rb +3 -0
  18. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  19. data/spec/graphql/query/executor_spec.rb +90 -0
  20. data/spec/graphql/query_spec.rb +65 -118
  21. data/spec/graphql/schema/type_validator_spec.rb +20 -7
  22. data/spec/support/dairy_app.rb +11 -0
  23. metadata +15 -10
  24. data/lib/graphql/query/field_resolution_strategy.rb +0 -87
  25. data/lib/graphql/query/fragment_spread_resolution_strategy.rb +0 -16
  26. data/lib/graphql/query/inline_fragment_resolution_strategy.rb +0 -14
  27. data/lib/graphql/query/operation_resolver.rb +0 -26
  28. data/lib/graphql/query/selection_resolver.rb +0 -20
  29. data/spec/graphql/query/operation_resolver_spec.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08f966586efa49fab35baf8a07f91bf1a8f1b90d
4
- data.tar.gz: 1906d5e21b82db60e65acea99164cf8bb2f8e946
3
+ metadata.gz: 597ceac7f7f1c006aaaf98ff69fb2320e80c08fb
4
+ data.tar.gz: 8145bb6df94810a05738c2dbde3753c7c8e8e26d
5
5
  SHA512:
6
- metadata.gz: c191f4862f354f98264c330040e001d7b5eeea2e5c97008bf13d19b196213d6018e4f9e4f48b0e11cb066b0851857791bc445af114caef4560213dbfe6f71436
7
- data.tar.gz: e1ee28e2a7450bd018aa32816f7e8461791c8e236a5afa443955a594430d9ae7a12faa567b4056261d2903936e9404cbd1367bf3a13b5562d7ac3b68e2d95ad0
6
+ metadata.gz: 1127f6886b67be9890dfef2db3708210a319f292fafb35d49dd4f0092330717c783e886710f7573353b7582e8a3fa743367c70ca69e0740afbc11f5a87b77920
7
+ data.tar.gz: 2ff711f09df54c8e91c97045c72780d6921fcfefce04ebadeb8368b56452e4300680c64689f48b2acadd38686ec3e9b50c72dde5bfe6e780ff891d687367afe1
data/lib/graphql/query.rb CHANGED
@@ -3,7 +3,7 @@ class GraphQL::Query
3
3
  # The executor will send the field's name to the target object
4
4
  # and use the result.
5
5
  DEFAULT_RESOLVE = :__default_resolve
6
- attr_reader :schema, :document, :context, :fragments, :variables
6
+ attr_reader :schema, :document, :context, :fragments, :variables, :operations, :debug
7
7
 
8
8
  # Prepare query `query_string` on `schema`
9
9
  # @param schema [GraphQL::Schema]
@@ -13,17 +13,12 @@ class GraphQL::Query
13
13
  # @param debug [Boolean] if true, errors are raised, if false, errors are put in the `errors` key
14
14
  # @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
15
15
  # @param operation_name [String] if the query string contains many operations, this is the one which should be executed
16
- def initialize(schema, query_string, context: nil, params: nil, variables: {}, debug: true, validate: true, operation_name: nil)
16
+ def initialize(schema, query_string, context: nil, variables: {}, debug: true, validate: true, operation_name: nil)
17
17
  @schema = schema
18
18
  @debug = debug
19
19
  @context = Context.new(context)
20
20
 
21
21
  @variables = variables
22
- if params
23
- warn("[GraphQL] params option is deprecated for GraphQL::Query#new, use variables instead")
24
- @variables = params
25
- end
26
-
27
22
  @validate = validate
28
23
  @operation_name = operation_name
29
24
  @fragments = {}
@@ -45,39 +40,15 @@ class GraphQL::Query
45
40
  return { "errors" => validation_errors }
46
41
  end
47
42
 
48
- @result ||= { "data" => execute }
49
-
50
- rescue OperationNameMissingError => err
51
- {"errors" => [{"message" => err.message}]}
52
- rescue StandardError => err
53
- @debug && raise(err)
54
- message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
55
- {"errors" => [{"message" => message}]}
43
+ @result ||= Executor.new(self, @operation_name).result
56
44
  end
57
45
 
58
46
  private
59
47
 
60
- def execute
61
- return {} if @operations.none?
62
- operation = find_operation(@operation_name, @operations)
63
- resolver = OperationResolver.new(operation, self)
64
- resolver.result
65
- end
66
-
67
48
  def validation_errors
68
49
  @validation_errors ||= @schema.static_validator.validate(@document)
69
50
  end
70
51
 
71
- def find_operation(operation_name, operations)
72
- if operations.length == 1
73
- operations.values.first
74
- elsif !operations.key?(operation_name)
75
- raise OperationNameMissingError, operations.keys
76
- else
77
- operations[operation_name]
78
- end
79
- end
80
-
81
52
  # Expose some query-specific info to field resolve functions.
82
53
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
83
54
  class Context
@@ -89,20 +60,12 @@ class GraphQL::Query
89
60
  @arbitrary_hash[key]
90
61
  end
91
62
  end
92
-
93
- class OperationNameMissingError < StandardError
94
- def initialize(names)
95
- msg = "You must provide an operation name from: #{names.join(", ")}"
96
- super(msg)
97
- end
98
- end
99
63
  end
100
64
 
101
65
  require 'graphql/query/arguments'
102
- require 'graphql/query/field_resolution_strategy'
103
- require 'graphql/query/fragment_spread_resolution_strategy'
104
- require 'graphql/query/inline_fragment_resolution_strategy'
105
- require 'graphql/query/operation_resolver'
106
- require 'graphql/query/selection_resolver'
66
+ require 'graphql/query/base_execution'
67
+ require 'graphql/query/serial_execution'
68
+ require 'graphql/query/value_resolution'
107
69
  require 'graphql/query/type_resolver'
108
70
  require 'graphql/query/directive_chain'
71
+ require 'graphql/query/executor'
@@ -0,0 +1,34 @@
1
+ module GraphQL
2
+ class Query
3
+ class BaseExecution
4
+
5
+ def field_resolution
6
+ get_class :FieldResolution
7
+ end
8
+
9
+ def fragment_spread_resolution
10
+ get_class :FragmentSpreadResolution
11
+ end
12
+
13
+ def inline_fragment_resolution
14
+ get_class :InlineFragmentResolution
15
+ end
16
+
17
+ def operation_resolution
18
+ get_class :OperationResolution
19
+ end
20
+
21
+ def selection_resolution
22
+ get_class :SelectionResolution
23
+ end
24
+
25
+ private
26
+
27
+ def get_class(class_name)
28
+ self.class.const_get(class_name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'graphql/query/base_execution/selected_object_resolution'
@@ -0,0 +1,16 @@
1
+ module GraphQL
2
+ class Query
3
+ class BaseExecution
4
+ class SelectedObjectResolution
5
+ attr_reader :ast_node, :parent_type, :target, :query, :execution_strategy
6
+ def initialize(ast_node, parent_type, target, query, execution_strategy)
7
+ @ast_node = ast_node
8
+ @parent_type = parent_type
9
+ @target = target
10
+ @query = query
11
+ @execution_strategy = execution_strategy
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,4 @@
1
+ # TODO: directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
1
2
  class GraphQL::Query::DirectiveChain
2
3
  DIRECTIVE_ON = {
3
4
  GraphQL::Language::Nodes::Field => GraphQL::Directive::ON_FIELD,
@@ -12,10 +13,15 @@ class GraphQL::Query::DirectiveChain
12
13
  }
13
14
 
14
15
  attr_reader :result
15
- def initialize(ast_node, operation_resolver, &block)
16
- directives = operation_resolver.query.schema.directives
16
+ def initialize(ast_node, query, &block)
17
+ directives = query.schema.directives
17
18
  on_what = DIRECTIVE_ON[ast_node.class]
18
- ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node, operation_resolver.query.fragments)
19
+ ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node, query.fragments)
20
+
21
+ if contains_skip?(ast_directives)
22
+ ast_directives = ast_directives.reject { |ast_directive| ast_directive.name == 'include' }
23
+ end
24
+
19
25
  applicable_directives = ast_directives
20
26
  .map { |ast_directive| [ast_directive, directives[ast_directive.name]] }
21
27
  .select { |directive_pair| directive_pair.last.on.include?(on_what) }
@@ -24,10 +30,15 @@ class GraphQL::Query::DirectiveChain
24
30
  @result = block.call
25
31
  else
26
32
  applicable_directives.map do |(ast_directive, directive)|
27
- args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables)
33
+ args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, query.variables)
28
34
  @result = directive.resolve(args, block)
29
35
  end
30
36
  @result ||= {}
31
37
  end
32
38
  end
39
+
40
+ private
41
+ def contains_skip?(directives)
42
+ directives.any? { |directive| directive.name == 'skip' }
43
+ end
33
44
  end
@@ -0,0 +1,53 @@
1
+ module GraphQL
2
+ class Query
3
+ class Executor
4
+ class OperationNameMissingError < StandardError
5
+ def initialize(names)
6
+ msg = "You must provide an operation name from: #{names.join(", ")}"
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ attr_reader :query, :operation_name
12
+ def initialize(query, operation_name)
13
+ @query = query
14
+ @operation_name = operation_name
15
+ end
16
+
17
+ def result
18
+ {"data" => execute }
19
+ rescue OperationNameMissingError => err
20
+ {"errors" => [{"message" => err.message}]}
21
+ rescue StandardError => err
22
+ query.debug && raise(err)
23
+ message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
24
+ {"errors" => [{"message" => message}]}
25
+ end
26
+
27
+ private
28
+
29
+ def execute
30
+ return {} if query.operations.none?
31
+ operation = find_operation(operation_name, query.operations)
32
+ if operation.operation_type == "query"
33
+ root = query.schema.query
34
+ elsif operation.operation_type == "mutation"
35
+ root = query.schema.mutation
36
+ end
37
+ execution_strategy = GraphQL::Query::SerialExecution.new
38
+ resolver = execution_strategy.operation_resolution.new(operation, root, query, execution_strategy)
39
+ resolver.result
40
+ end
41
+
42
+ def find_operation(operation_name, operations)
43
+ if operations.length == 1
44
+ operations.values.first
45
+ elsif !operations.key?(operation_name)
46
+ raise OperationNameMissingError, operations.keys
47
+ else
48
+ operations[operation_name]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,12 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution < GraphQL::Query::BaseExecution
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'graphql/query/serial_execution/field_resolution'
9
+ require 'graphql/query/serial_execution/fragment_spread_resolution'
10
+ require 'graphql/query/serial_execution/inline_fragment_resolution'
11
+ require 'graphql/query/serial_execution/operation_resolution'
12
+ require 'graphql/query/serial_execution/selection_resolution'
@@ -0,0 +1,42 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class FieldResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :field, :arguments
6
+
7
+ def initialize(ast_node, parent_type, target, query, execution_strategy)
8
+ super
9
+ @field = query.schema.get_field(parent_type, ast_node.name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'")
10
+ @arguments = GraphQL::Query::Arguments.new(ast_node.arguments, field.arguments, query.variables)
11
+ end
12
+
13
+ def result
14
+ result_name = ast_node.alias || ast_node.name
15
+ { result_name => result_value}
16
+ end
17
+
18
+ private
19
+
20
+ def result_value
21
+ value = field.resolve(target, arguments, query.context)
22
+
23
+ if value == GraphQL::Query::DEFAULT_RESOLVE
24
+ begin
25
+ value = target.public_send(ast_node.name)
26
+ rescue NoMethodError => err
27
+ raise("Couldn't resolve field '#{ast_node.name}' to #{target.class} '#{target}' (resulted in #{err})")
28
+ end
29
+ end
30
+
31
+ return nil if value.nil?
32
+
33
+
34
+ resolved_type = field.type.kind.resolve(field.type, value)
35
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
36
+ result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_node, query, execution_strategy)
37
+ result_strategy.result
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class FragmentSpreadResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :ast_fragment, :resolved_type
6
+ def initialize(ast_node, type, target, query, execution_strategy)
7
+ super
8
+ @ast_fragment = query.fragments[ast_node.name]
9
+ child_type = query.schema.types[ast_fragment.type]
10
+ @resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
11
+ end
12
+
13
+ def result
14
+ return {} if resolved_type.nil?
15
+ selections = ast_fragment.selections
16
+ resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
17
+ resolver.result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class InlineFragmentResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :resolved_type
6
+ def initialize(ast_node, type, target, query, execution_strategy)
7
+ super
8
+ child_type = query.schema.types[ast_node.type]
9
+ @resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
10
+ end
11
+
12
+ def result
13
+ return {} if resolved_type.nil?
14
+ selections = ast_node.selections
15
+ resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
16
+ resolver.result
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class OperationResolution
5
+ attr_reader :query, :target, :ast_operation_definition, :execution_strategy
6
+
7
+ def initialize(ast_operation_definition, target, query, execution_strategy)
8
+ @ast_operation_definition = ast_operation_definition
9
+ @query = query
10
+ @target = target
11
+ @execution_strategy = execution_strategy
12
+ end
13
+
14
+ def result
15
+ selections = ast_operation_definition.selections
16
+ resolver = execution_strategy.selection_resolution.new(nil, target, selections, query, execution_strategy)
17
+ resolver.result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class SelectionResolution
5
+ attr_reader :target, :type, :selections, :query, :execution_strategy
6
+
7
+ RESOLUTION_STRATEGIES = {
8
+ GraphQL::Language::Nodes::Field => :field_resolution,
9
+ GraphQL::Language::Nodes::FragmentSpread => :fragment_spread_resolution,
10
+ GraphQL::Language::Nodes::InlineFragment => :inline_fragment_resolution,
11
+ }
12
+
13
+ def initialize(target, type, selections, query, execution_strategy)
14
+ @target = target
15
+ @type = type
16
+ @selections = selections
17
+ @query = query
18
+ @execution_strategy = execution_strategy
19
+ end
20
+
21
+ def result
22
+ selections.reduce({}) do |memo, ast_field|
23
+ field_value = resolve_field(ast_field)
24
+ memo.merge(field_value)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def resolve_field(ast_field)
31
+ chain = GraphQL::Query::DirectiveChain.new(ast_field, query) {
32
+ strategy_name = RESOLUTION_STRATEGIES[ast_field.class]
33
+ strategy_class = execution_strategy.public_send(strategy_name)
34
+ strategy = strategy_class.new(ast_field, type, target, query, execution_strategy)
35
+ strategy.result
36
+ }
37
+ chain.result
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ module GraphQL
2
+ class Query
3
+ module ValueResolution
4
+ def self.get_strategy_for_kind(kind)
5
+ TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
6
+ end
7
+
8
+ class BaseResolution
9
+ attr_reader :value, :field_type, :target, :parent_type,
10
+ :ast_field, :query, :execution_strategy
11
+ def initialize(value, field_type, target, parent_type, ast_field, query, execution_strategy)
12
+ @value = value
13
+ @field_type = field_type
14
+ @target = target
15
+ @parent_type = parent_type
16
+ @ast_field = ast_field
17
+ @query = query
18
+ @execution_strategy = execution_strategy
19
+ end
20
+
21
+ def result
22
+ raise NotImplementedError, "Should return a value based on initialization params"
23
+ end
24
+ end
25
+
26
+ class ScalarResolution < BaseResolution
27
+ def result
28
+ field_type.coerce(value)
29
+ end
30
+ end
31
+
32
+ class ListResolution < BaseResolution
33
+ def result
34
+ wrapped_type = field_type.of_type
35
+ value.map do |item|
36
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
37
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
38
+ inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, query, execution_strategy)
39
+ inner_strategy.result
40
+ end
41
+ end
42
+ end
43
+
44
+ class ObjectResolution < BaseResolution
45
+ def result
46
+ resolver = execution_strategy.selection_resolution.new(value, field_type, ast_field.selections, query, execution_strategy)
47
+ resolver.result
48
+ end
49
+ end
50
+
51
+ class EnumResolution < BaseResolution
52
+ def result
53
+ value.to_s
54
+ end
55
+ end
56
+
57
+ class NonNullResolution < BaseResolution
58
+ def result
59
+ wrapped_type = field_type.of_type
60
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
61
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
62
+ inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, query, execution_strategy)
63
+ inner_strategy.result
64
+ end
65
+ end
66
+
67
+ TYPE_KIND_STRATEGIES = {
68
+ GraphQL::TypeKinds::SCALAR => ScalarResolution,
69
+ GraphQL::TypeKinds::LIST => ListResolution,
70
+ GraphQL::TypeKinds::OBJECT => ObjectResolution,
71
+ GraphQL::TypeKinds::ENUM => EnumResolution,
72
+ GraphQL::TypeKinds::NON_NULL => NonNullResolution,
73
+ }
74
+ end
75
+ end
76
+ end
@@ -45,8 +45,8 @@ class GraphQL::Schema::TypeValidator
45
45
 
46
46
  if type.kind.union?
47
47
  union_types = type.possible_types
48
- if union_types.length < 2
49
- own_errors << "Union #{type_name} must be defined with 2 or more types, not #{union_types.length}"
48
+ if union_types.none?
49
+ own_errors << "Union #{type_name} must be defined with 1 or more types, not 0!"
50
50
  end
51
51
  end
52
52
  errors.push(*own_errors)
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
data/readme.md CHANGED
@@ -91,28 +91,34 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
91
91
 
92
92
  ## To Do
93
93
 
94
- - To match spec:
95
- - Directives:
96
- - `@skip` has precedence over `@include`
97
- - directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
98
- - Union
99
- - only require one type https://github.com/facebook/graphql/commit/972fd5aae01195b018d305c82e43f714f9a41679
100
94
  - Field merging
101
95
  - if you were to request a field, then request it in a fragment, it would get looked up twice
102
96
  - https://github.com/graphql/graphql-js/issues/19#issuecomment-118515077
103
97
  - Code clean-up
104
- - Accept native Ruby types in definitions, then convert them to GraphQL types
105
- - Remove deprecated `params:` keyword
98
+ - Easier built-in type definition
99
+ - Make an object that accepts type objects, symbols, or corresponding Ruby classes and convertes them to GraphQL types
100
+ - Hook up that object to `DefinitionConfig`, so it can map from incoming values to GraphQL types
106
101
  - Raise if you try to configure an attribute which doesn't suit the type
107
- - Cook up some path other than "n+1s everywhere"
108
- - See Sangria's `project` approach ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/15))
109
- - Try debounced approach?
102
+ - ie, if you try to define `resolve` on an ObjectType, it should somehow raise
103
+ - Make better inheritance between types
104
+ - Implement a BaseType (?) and make all type classes extend that
105
+ - No more extending ObjectType!
106
+ - Move `TypeKind#unwrap` to BaseType & update all code
107
+ - Also move `TypeKind#resolve` ?
108
+ - Big ideas:
109
+ - Cook up some path other than "n+1s everywhere"
110
+ - See Sangria's `project` approach ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/15))
111
+ - Try debounced approach?
112
+ - Write Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser) and use that instead of Parslet
113
+ - Add instrumentation
114
+ - Some way to expose what queries are run, what types & fields are accessed, how long things are taking, etc
115
+
110
116
 
111
117
  ## Goals
112
118
 
113
119
  - Implement the GraphQL spec & support a Relay front end
114
120
  - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible
115
- - Support `graphql-rails`
121
+ - Support Ruby on Rails and Relay
116
122
 
117
123
  ## Getting Involved
118
124
 
@@ -8,6 +8,8 @@ describe GraphQL::Directive do
8
8
  # plain fields:
9
9
  skipFlavor: flavor @skip(if: true)
10
10
  dontSkipFlavor: flavor @skip(if: false)
11
+ dontSkipDontIncludeFlavor: flavor @skip(if: false), @include(if: false)
12
+ skipAndInclude: flavor @skip(if: true), @include(if: true)
11
13
  includeFlavor: flavor @include(if: $t)
12
14
  dontIncludeFlavor: flavor @include(if: $f)
13
15
  # fields in fragments
@@ -25,6 +27,7 @@ describe GraphQL::Directive do
25
27
  it 'intercepts fields' do
26
28
  expected = { "data" =>{
27
29
  "cheese" => {
30
+ "dontSkipDontIncludeFlavor" => "Brie", #skip has precedence over include
28
31
  "dontSkipFlavor" => "Brie",
29
32
  "includeFlavor" => "Brie",
30
33
  "includeId" => 1,
@@ -23,6 +23,7 @@ describe GraphQL::Introspection::SchemaType do
23
23
  {"name"=>"favoriteEdible"},
24
24
  {"name"=>"searchDairy"},
25
25
  {"name"=>"error"},
26
+ {"name"=>"maybeNull"},
26
27
  ]
27
28
  },
28
29
  "mutationType"=> {
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Query::Executor do
4
+ let(:debug) { false }
5
+ let(:operation_name) { nil }
6
+ let(:query) { GraphQL::Query.new(
7
+ DummySchema,
8
+ query_string,
9
+ variables: {"cheeseId" => 2},
10
+ debug: debug,
11
+ operation_name: operation_name,
12
+ )}
13
+ let(:result) { query.result }
14
+
15
+ describe "multiple operations" do
16
+ let(:query_string) { %|
17
+ query getCheese1 { cheese(id: 1) { flavor } }
18
+ query getCheese2 { cheese(id: 2) { flavor } }
19
+ |}
20
+
21
+ describe "when an operation is named" do
22
+ let(:operation_name) { "getCheese2" }
23
+
24
+ it "runs the named one" do
25
+ expected = {
26
+ "data" => {
27
+ "cheese" => {
28
+ "flavor" => "Gouda"
29
+ }
30
+ }
31
+ }
32
+ assert_equal(expected, result)
33
+ end
34
+ end
35
+
36
+ describe "when one is NOT named" do
37
+ it "returns an error" do
38
+ expected = {
39
+ "errors" => [
40
+ {"message" => "You must provide an operation name from: getCheese1, getCheese2"}
41
+ ]
42
+ }
43
+ assert_equal(expected, result)
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ describe 'execution order' do
50
+ let(:query_string) {%|
51
+ mutation setInOrder {
52
+ first: pushValue(value: 1)
53
+ second: pushValue(value: 5)
54
+ third: pushValue(value: 2)
55
+ fourth: replaceValues(input: {values: [6,5,4]})
56
+ }
57
+ |}
58
+
59
+ it 'executes mutations in order' do
60
+ expected = {"data"=>{
61
+ "first"=> [1],
62
+ "second"=>[1, 5],
63
+ "third"=> [1, 5, 2],
64
+ "fourth"=> [6, 5 ,4],
65
+ }}
66
+ assert_equal(expected, result)
67
+ end
68
+ end
69
+
70
+
71
+ describe 'runtime errors' do
72
+ let(:query_string) {%| query noMilk { error }|}
73
+ describe 'if debug: false' do
74
+ let(:debug) { false }
75
+ it 'turns into error messages' do
76
+ expected = {"errors"=>[
77
+ {"message"=>"Something went wrong during query execution: This error was raised on purpose"}
78
+ ]}
79
+ assert_equal(expected, result)
80
+ end
81
+ end
82
+
83
+ describe 'if debug: true' do
84
+ let(:debug) { true }
85
+ it 'raises error' do
86
+ assert_raises(RuntimeError) { result }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,45 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Query do
4
- describe '#execute' do
5
- let(:query_string) { %|
6
- query getFlavor($cheeseId: Int!) {
7
- brie: cheese(id: 1) { ...cheeseFields, taste: flavor },
8
- cheese(id: $cheeseId) {
9
- __typename,
10
- id,
11
- ...cheeseFields,
12
- ... edibleFields,
13
- ... on Cheese { cheeseKind: flavor },
14
- }
15
- fromSource(source: COW) { id }
16
- fromSheep: fromSource(source: SHEEP) { id }
17
- firstSheep: searchDairy(product: {source: SHEEP}) {
18
- __typename,
19
- ... dairyFields,
20
- ... milkFields
21
- }
22
- favoriteEdible { __typename, fatContent }
4
+ let(:query_string) { %|
5
+ query getFlavor($cheeseId: Int!) {
6
+ brie: cheese(id: 1) { ...cheeseFields, taste: flavor },
7
+ cheese(id: $cheeseId) {
8
+ __typename,
9
+ id,
10
+ ...cheeseFields,
11
+ ... edibleFields,
12
+ ... on Cheese { cheeseKind: flavor },
23
13
  }
24
- fragment cheeseFields on Cheese { flavor }
25
- fragment edibleFields on Edible { fatContent }
26
- fragment milkFields on Milk { source }
27
- fragment dairyFields on AnimalProduct {
28
- ... on Cheese { flavor }
29
- ... on Milk { source }
14
+ fromSource(source: COW) { id }
15
+ fromSheep: fromSource(source: SHEEP) { id }
16
+ firstSheep: searchDairy(product: {source: SHEEP}) {
17
+ __typename,
18
+ ... dairyFields,
19
+ ... milkFields
30
20
  }
31
- |}
32
- let(:debug) { false }
33
- let(:operation_name) { nil }
34
- let(:query) { GraphQL::Query.new(
35
- DummySchema,
36
- query_string,
37
- variables: {"cheeseId" => 2},
38
- debug: debug,
39
- operation_name: operation_name,
40
- )}
41
- let(:result) { query.result }
42
-
21
+ favoriteEdible { __typename, fatContent }
22
+ }
23
+ fragment cheeseFields on Cheese { flavor }
24
+ fragment edibleFields on Edible { fatContent }
25
+ fragment milkFields on Milk { source }
26
+ fragment dairyFields on AnimalProduct {
27
+ ... on Cheese { flavor }
28
+ ... on Milk { source }
29
+ }
30
+ |}
31
+ let(:debug) { false }
32
+ let(:operation_name) { nil }
33
+ let(:query) { GraphQL::Query.new(
34
+ DummySchema,
35
+ query_string,
36
+ variables: {"cheeseId" => 2},
37
+ debug: debug,
38
+ operation_name: operation_name,
39
+ )}
40
+ let(:result) { query.result }
41
+ describe '#result' do
43
42
  it 'returns fields on objects' do
44
43
  expected = {"data"=> {
45
44
  "brie" => { "flavor" => "Brie", "taste" => "Brie" },
@@ -58,96 +57,44 @@ describe GraphQL::Query do
58
57
  assert_equal(expected, result)
59
58
  end
60
59
 
61
- it 'exposes fragments' do
62
- assert_equal(GraphQL::Language::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
63
- end
64
-
65
- describe 'runtime errors' do
66
- let(:query_string) {%| query noMilk { error }|}
67
- describe 'if debug: false' do
68
- let(:debug) { false }
69
- it 'turns into error messages' do
70
- expected = {"errors"=>[
71
- {"message"=>"Something went wrong during query execution: This error was raised on purpose"}
72
- ]}
73
- assert_equal(expected, result)
74
- end
75
- end
60
+ describe "when it hits null objects" do
61
+ let(:query_string) {%|
62
+ {
63
+ maybeNull {
64
+ cheese {
65
+ flavor,
66
+ similarCheeses(source: [SHEEP]) { flavor }
67
+ }
68
+ }
69
+ }
70
+ |}
76
71
 
77
- describe 'if debug: true' do
78
- let(:debug) { true }
79
- it 'raises error' do
80
- assert_raises(RuntimeError) { result }
81
- end
72
+ it "skips null objects" do
73
+ expected = {"data"=> {
74
+ "maybeNull" => { "cheese" => nil }
75
+ }}
76
+ assert_equal(expected, result)
82
77
  end
83
78
  end
79
+ end
84
80
 
85
- describe "malformed queries" do
86
- describe "whitespace-only" do
87
- let(:query_string) { " " }
88
- it "doesn't blow up" do
89
- assert_equal({"data"=> {}}, result)
90
- end
91
- end
92
-
93
- describe "empty string" do
94
- let(:query_string) { "" }
95
- it "doesn't blow up" do
96
- assert_equal({"data"=> {}}, result)
97
- end
98
- end
99
- end
81
+ it 'exposes fragments' do
82
+ assert_equal(GraphQL::Language::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
83
+ end
100
84
 
101
- describe "multiple operations" do
102
- let(:query_string) { %|
103
- query getCheese1 { cheese(id: 1) { flavor } }
104
- query getCheese2 { cheese(id: 2) { flavor } }
105
- |}
106
- describe "when an operation is named" do
107
- let(:operation_name) { "getCheese2" }
108
- it "runs the named one" do
109
- expected = {
110
- "data" => {
111
- "cheese" => {
112
- "flavor" => "Gouda"
113
- }
114
- }
115
- }
116
- assert_equal(expected, result)
117
- end
118
- end
119
85
 
120
- describe "when one is NOT named" do
121
- it "returns an error" do
122
- expected = {
123
- "errors" => [
124
- {"message" => "You must provide an operation name from: getCheese1, getCheese2"}
125
- ]
126
- }
127
- assert_equal(expected, result)
128
- end
86
+ describe "malformed queries" do
87
+ describe "whitespace-only" do
88
+ let(:query_string) { " " }
89
+ it "doesn't blow up" do
90
+ assert_equal({"data"=> {}}, result)
129
91
  end
130
92
  end
131
93
 
132
-
133
- describe 'execution order' do
134
- let(:query_string) {%|
135
- mutation setInOrder {
136
- first: pushValue(value: 1)
137
- second: pushValue(value: 5)
138
- third: pushValue(value: 2)
139
- fourth: replaceValues(input: {values: [6,5,4]})
140
- }
141
- |}
142
-
143
- it 'executes mutations in order' do
144
- expected = {"data"=>{
145
- "first"=> [1],
146
- "second"=>[1, 5],
147
- "third"=> [1, 5, 2],
148
- "fourth"=> [6, 5 ,4],
149
- }}
150
- assert_equal(expected, result)
94
+ describe "empty string" do
95
+ let(:query_string) { "" }
96
+ it "doesn't blow up" do
97
+ assert_equal({"data"=> {}}, result)
151
98
  end
152
99
  end
153
100
  end
@@ -50,19 +50,32 @@ describe GraphQL::Schema::TypeValidator do
50
50
 
51
51
  describe "when a Union isnt valid" do
52
52
  let(:object) {
53
+ union_types = types
53
54
  GraphQL::UnionType.define do
54
55
  name "Something"
55
56
  description "some union"
56
- possible_types [DairyProductInputType]
57
+ possible_types union_types
57
58
  end
58
59
  }
59
60
  let(:errors) { e = []; GraphQL::Schema::TypeValidator.new.validate(object, e); e;}
60
- it 'must be 2+ types, must be only object types' do
61
- expected = [
62
- "Something.possible_types must be objects, but some aren't: DairyProductInput",
63
- "Union Something must be defined with 2 or more types, not 1",
64
- ]
65
- assert_equal(expected, errors)
61
+
62
+ describe 'when non-object types' do
63
+ let(:types) { [DairyProductInputType] }
64
+ it 'must be must be only object types' do
65
+ expected = [
66
+ "Something.possible_types must be objects, but some aren't: DairyProductInput"
67
+ ]
68
+ assert_equal(expected, errors)
69
+ end
70
+ end
71
+ describe 'when no types' do
72
+ let(:types) { [] }
73
+ it 'must have a type' do
74
+ expected = [
75
+ "Union Something must be defined with 1 or more types, not 0!"
76
+ ]
77
+ assert_equal(expected, errors)
78
+ end
66
79
  end
67
80
  end
68
81
  end
@@ -62,6 +62,12 @@ MilkType = GraphQL::ObjectType.define do
62
62
  end
63
63
  end
64
64
 
65
+ MaybeNullType = GraphQL::ObjectType.define do
66
+ name "MaybeNull"
67
+ description "An object whose fields return nil"
68
+ field :cheese, CheeseType
69
+ end
70
+
65
71
  DairyProductUnion = GraphQL::UnionType.define do
66
72
  name "DairyProduct"
67
73
  description "Kinds of food made from milk"
@@ -131,6 +137,11 @@ QueryType = GraphQL::ObjectType.define do
131
137
  type GraphQL::STRING_TYPE
132
138
  resolve -> (t, a, c) { raise("This error was raised on purpose") }
133
139
  end
140
+
141
+ # To test possibly-null fields
142
+ field :maybeNull, MaybeNullType do
143
+ resolve -> (t, a, c) { OpenStruct.new(cheese: nil) }
144
+ end
134
145
  end
135
146
 
136
147
  GLOBAL_VALUES = []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-20 00:00:00.000000000 Z
11
+ date: 2015-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet
@@ -206,13 +206,18 @@ files:
206
206
  - lib/graphql/object_type.rb
207
207
  - lib/graphql/query.rb
208
208
  - lib/graphql/query/arguments.rb
209
+ - lib/graphql/query/base_execution.rb
210
+ - lib/graphql/query/base_execution/selected_object_resolution.rb
209
211
  - lib/graphql/query/directive_chain.rb
210
- - lib/graphql/query/field_resolution_strategy.rb
211
- - lib/graphql/query/fragment_spread_resolution_strategy.rb
212
- - lib/graphql/query/inline_fragment_resolution_strategy.rb
213
- - lib/graphql/query/operation_resolver.rb
214
- - lib/graphql/query/selection_resolver.rb
212
+ - lib/graphql/query/executor.rb
213
+ - lib/graphql/query/serial_execution.rb
214
+ - lib/graphql/query/serial_execution/field_resolution.rb
215
+ - lib/graphql/query/serial_execution/fragment_spread_resolution.rb
216
+ - lib/graphql/query/serial_execution/inline_fragment_resolution.rb
217
+ - lib/graphql/query/serial_execution/operation_resolution.rb
218
+ - lib/graphql/query/serial_execution/selection_resolution.rb
215
219
  - lib/graphql/query/type_resolver.rb
220
+ - lib/graphql/query/value_resolution.rb
216
221
  - lib/graphql/repl.rb
217
222
  - lib/graphql/scalar_type.rb
218
223
  - lib/graphql/schema.rb
@@ -263,7 +268,7 @@ files:
263
268
  - spec/graphql/language/transform_spec.rb
264
269
  - spec/graphql/language/visitor_spec.rb
265
270
  - spec/graphql/object_type_spec.rb
266
- - spec/graphql/query/operation_resolver_spec.rb
271
+ - spec/graphql/query/executor_spec.rb
267
272
  - spec/graphql/query_spec.rb
268
273
  - spec/graphql/schema/field_validator_spec.rb
269
274
  - spec/graphql/schema/type_reducer_spec.rb
@@ -312,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
312
317
  version: '0'
313
318
  requirements: []
314
319
  rubyforge_project:
315
- rubygems_version: 2.4.5
320
+ rubygems_version: 2.2.0
316
321
  signing_key:
317
322
  specification_version: 4
318
323
  summary: A GraphQL implementation for Ruby
@@ -331,7 +336,7 @@ test_files:
331
336
  - spec/graphql/language/transform_spec.rb
332
337
  - spec/graphql/language/visitor_spec.rb
333
338
  - spec/graphql/object_type_spec.rb
334
- - spec/graphql/query/operation_resolver_spec.rb
339
+ - spec/graphql/query/executor_spec.rb
335
340
  - spec/graphql/query_spec.rb
336
341
  - spec/graphql/schema/field_validator_spec.rb
337
342
  - spec/graphql/schema/type_reducer_spec.rb
@@ -1,87 +0,0 @@
1
- class GraphQL::Query::FieldResolutionStrategy
2
- attr_reader :result, :result_value
3
-
4
- def initialize(ast_field, parent_type, target, operation_resolver)
5
- field_name = ast_field.name
6
- field = operation_resolver.query.schema.get_field(parent_type, field_name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
7
- arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables)
8
- value = field.resolve(target, arguments, operation_resolver.context)
9
- if value.nil?
10
- @result_value = value
11
- else
12
- if value == GraphQL::Query::DEFAULT_RESOLVE
13
- begin
14
- value = target.send(field_name)
15
- rescue NoMethodError => err
16
- raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
17
- end
18
- end
19
- resolved_type = field.type.kind.resolve(field.type, value)
20
- strategy_class = self.class.get_strategy_for_kind(resolved_type.kind)
21
- result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
22
- @result_value = result_strategy.result
23
- end
24
- result_name = ast_field.alias || ast_field.name
25
- @result = { result_name => @result_value}
26
- end
27
-
28
- def self.get_strategy_for_kind(kind)
29
- FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
30
- end
31
-
32
- class ScalarResolutionStrategy
33
- attr_reader :result
34
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
35
- @result = field_type.coerce(value)
36
- end
37
- end
38
-
39
- class ListResolutionStrategy
40
- attr_reader :result
41
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
42
- wrapped_type = field_type.of_type
43
- @result = value.map do |item|
44
- resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
45
- strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
46
- inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
47
- inner_strategy.result
48
- end
49
- end
50
- end
51
-
52
- class ObjectResolutionStrategy
53
- attr_reader :result
54
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
55
- resolver = GraphQL::Query::SelectionResolver.new(value, field_type, ast_field.selections, operation_resolver)
56
- @result = resolver.result
57
- end
58
- end
59
-
60
- class EnumResolutionStrategy
61
- attr_reader :result
62
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
63
- @result = value.to_s
64
- end
65
- end
66
-
67
- class NonNullResolutionStrategy
68
- attr_reader :result
69
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
70
- wrapped_type = field_type.of_type
71
- resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
72
- strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
73
- inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
74
- @result = inner_strategy.result
75
- end
76
- end
77
-
78
- private
79
-
80
- FIELD_TYPE_KIND_STRATEGIES = {
81
- GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
82
- GraphQL::TypeKinds::LIST => ListResolutionStrategy,
83
- GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
84
- GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
85
- GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
86
- }
87
- end
@@ -1,16 +0,0 @@
1
- class GraphQL::Query::FragmentSpreadResolutionStrategy
2
- attr_reader :result
3
- def initialize(ast_fragment_spread, type, target, operation_resolver)
4
- fragments = operation_resolver.query.fragments
5
- fragment_def = fragments[ast_fragment_spread.name]
6
- child_type = operation_resolver.query.schema.types[fragment_def.type]
7
- resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
8
- if resolved_type.nil?
9
- @result = {}
10
- else
11
- selections = fragment_def.selections
12
- resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
13
- @result = resolver.result
14
- end
15
- end
16
- end
@@ -1,14 +0,0 @@
1
- class GraphQL::Query::InlineFragmentResolutionStrategy
2
- attr_reader :result
3
- def initialize(ast_inline_fragment, type, target, operation_resolver)
4
- child_type = operation_resolver.query.schema.types[ast_inline_fragment.type]
5
- resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
6
- if resolved_type.nil?
7
- @result = {}
8
- else
9
- selections = ast_inline_fragment.selections
10
- resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
11
- @result = resolver.result
12
- end
13
- end
14
- end
@@ -1,26 +0,0 @@
1
- class GraphQL::Query::OperationResolver
2
- attr_reader :variables, :query, :context
3
-
4
- def initialize(operation_definition, query)
5
- @operation_definition = operation_definition
6
- @variables = query.variables
7
- @query = query
8
- @context = query.context
9
- end
10
-
11
- def result
12
- @result ||= execute(@operation_definition, query)
13
- end
14
-
15
- private
16
-
17
- def execute(op_def, query)
18
- root = if op_def.operation_type == "query"
19
- query.schema.query
20
- elsif op_def.operation_type == "mutation"
21
- query.schema.mutation
22
- end
23
- resolver = GraphQL::Query::SelectionResolver.new(nil, root, op_def.selections, self)
24
- resolver.result
25
- end
26
- end
@@ -1,20 +0,0 @@
1
- class GraphQL::Query::SelectionResolver
2
- attr_reader :result
3
-
4
- RESOLUTION_STRATEGIES = {
5
- GraphQL::Language::Nodes::Field => GraphQL::Query::FieldResolutionStrategy,
6
- GraphQL::Language::Nodes::FragmentSpread => GraphQL::Query::FragmentSpreadResolutionStrategy,
7
- GraphQL::Language::Nodes::InlineFragment => GraphQL::Query::InlineFragmentResolutionStrategy,
8
- }
9
-
10
- def initialize(target, type, selections, operation_resolver)
11
- @result = selections.reduce({}) do |memo, ast_field|
12
- chain = GraphQL::Query::DirectiveChain.new(ast_field, operation_resolver) {
13
- strategy_class = RESOLUTION_STRATEGIES[ast_field.class]
14
- strategy = strategy_class.new(ast_field, type, target, operation_resolver)
15
- strategy.result
16
- }
17
- memo.merge(chain.result)
18
- end
19
- end
20
- end
@@ -1,14 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe GraphQL::Query::OperationResolver do
4
- let(:operation) { GraphQL.parse("query getCheese($cheeseId: Int!) { cheese(id: $cheeseId) { name }}", as: :operation_definition) }
5
- let(:variables) { {"cheeseId" => 1}}
6
- let(:query) { OpenStruct.new(variables: variables, context: nil) }
7
- let(:resolver) { GraphQL::Query::OperationResolver.new(operation, query)}
8
-
9
- describe "variables" do
10
- it 'returns variables by name' do
11
- assert_equal(1, resolver.variables["cheeseId"])
12
- end
13
- end
14
- end