graphql 1.4.5 → 1.5.3

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -23,11 +23,14 @@ module GraphQL
23
23
 
24
24
  context.visitor[GraphQL::Language::Nodes::Document].leave << ->(doc_node, parent) {
25
25
  spreads_to_validate.each do |frag_spread|
26
- fragment_child_name = context.fragments[frag_spread.node.name].type.name
27
- fragment_child = context.warden.get_type(fragment_child_name)
28
- # Might be non-existent type name
29
- if fragment_child
30
- validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
26
+ frag_node = context.fragments[frag_spread.node.name]
27
+ if frag_node
28
+ fragment_child_name = frag_node.type.name
29
+ fragment_child = context.warden.get_type(fragment_child_name)
30
+ # Might be non-existent type name
31
+ if fragment_child
32
+ validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
33
+ end
31
34
  end
32
35
  end
33
36
  }
@@ -5,38 +5,13 @@ module GraphQL
5
5
  include GraphQL::StaticValidation::Message::MessageHelper
6
6
 
7
7
  def validate(context)
8
- context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, parent) {
9
- if has_nested_spread?(node, [], context)
10
- context.errors << message("Fragment #{node.name} contains an infinite loop", node, context: context)
8
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) do
9
+ dependency_map = context.dependencies
10
+ dependency_map.cyclical_definitions.each do |defn|
11
+ if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
12
+ context.errors << message("Fragment #{defn.name} contains an infinite loop", defn.node, path: defn.path)
13
+ end
11
14
  end
12
- }
13
- end
14
-
15
- private
16
-
17
- def has_nested_spread?(fragment_def, parent_fragment_names, context)
18
- nested_spreads = get_spreads(fragment_def.selections)
19
-
20
- nested_spreads.any? do |spread|
21
- nested_def = context.fragments[spread.name]
22
- if nested_def.nil?
23
- # this is another kind of error
24
- false
25
- else
26
- parent_fragment_names.include?(spread.name) || has_nested_spread?(nested_def, parent_fragment_names + [fragment_def.name], context)
27
- end
28
- end
29
- end
30
-
31
- # Find spreads contained in this selection & return them in a flat array
32
- def get_spreads(selection)
33
- case selection
34
- when GraphQL::Language::Nodes::FragmentSpread
35
- [selection]
36
- when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InlineFragment
37
- get_spreads(selection.selections)
38
- when Array
39
- selection.map { |s| get_spreads(s) }.flatten
40
15
  end
41
16
  end
42
17
  end
@@ -5,49 +5,19 @@ module GraphQL
5
5
  include GraphQL::StaticValidation::Message::MessageHelper
6
6
 
7
7
  def validate(context)
8
- v = context.visitor
9
- used_fragments = []
10
- defined_fragments = []
11
-
12
- v[GraphQL::Language::Nodes::Document] << ->(node, parent) {
13
- defined_fragments = node.definitions
14
- .select { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
15
- .map { |node| FragmentInstance.new(node: node, path: context.path) }
16
- }
17
-
18
- v[GraphQL::Language::Nodes::FragmentSpread] << ->(node, parent) {
19
- used_fragments << FragmentInstance.new(node: node, path: context.path)
20
- if defined_fragments.none? { |defn| defn.name == node.name }
21
- GraphQL::Language::Visitor::SKIP
8
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) do
9
+ dependency_map = context.dependencies
10
+ dependency_map.unmet_dependencies.each do |op_defn, spreads|
11
+ spreads.each do |fragment_spread|
12
+ context.errors << message("Fragment #{fragment_spread.name} was used, but not defined", fragment_spread.node, path: fragment_spread.path)
13
+ end
22
14
  end
23
- }
24
- v[GraphQL::Language::Nodes::Document].leave << ->(node, parent) { add_errors(context, used_fragments, defined_fragments) }
25
- end
26
-
27
- private
28
-
29
- def add_errors(context, used_fragments, defined_fragments)
30
- undefined_fragments = find_difference(used_fragments, defined_fragments.map(&:name))
31
- undefined_fragments.each do |fragment|
32
- context.errors << message("Fragment #{fragment.name} was used, but not defined", fragment.node, path: fragment.path)
33
- end
34
15
 
35
- unused_fragments = find_difference(defined_fragments, used_fragments.map(&:name))
36
- unused_fragments.each do |fragment|
37
- context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
38
- end
39
- end
40
-
41
- def find_difference(fragments, allowed_fragment_names)
42
- fragments.select {|f| f.name && !allowed_fragment_names.include?(f.name) }
43
- end
44
-
45
- class FragmentInstance
46
- attr_reader :name, :node, :path
47
- def initialize(node:, path:)
48
- @node = node
49
- @name = node.name
50
- @path = path
16
+ dependency_map.unused_dependencies.each do |fragment|
17
+ if !fragment.name.nil?
18
+ context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
19
+ end
20
+ end
51
21
  end
52
22
  end
53
23
  end
@@ -7,11 +7,11 @@ module GraphQL
7
7
  def validate(context)
8
8
  op_names = Hash.new { |h, k| h[k] = [] }
9
9
 
10
- context.visitor[GraphQL::Language::Nodes::OperationDefinition].enter << -> (node, _parent) {
10
+ context.visitor[GraphQL::Language::Nodes::OperationDefinition].enter << ->(node, _parent) {
11
11
  op_names[node.name] << node
12
12
  }
13
13
 
14
- context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, _parent) {
14
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(node, _parent) {
15
15
  op_count = op_names.values.inject(0) { |m, v| m += v.size }
16
16
 
17
17
  op_names.each do |name, nodes|
@@ -33,7 +33,10 @@ module GraphQL
33
33
  private
34
34
 
35
35
  def validate_usage(arguments, arg_node, ast_var, context)
36
- var_type = to_query_type(ast_var.type, context.schema.types)
36
+ var_type = to_query_type(ast_var.type, context.query.warden)
37
+ if var_type.nil?
38
+ return
39
+ end
37
40
  if !ast_var.default_value.nil?
38
41
  var_type = GraphQL::NonNullType.new(of_type: var_type)
39
42
  end
@@ -53,13 +56,22 @@ module GraphQL
53
56
  end
54
57
  end
55
58
 
56
- def to_query_type(ast_type, types)
57
- if ast_type.is_a?(GraphQL::Language::Nodes::NonNullType)
58
- GraphQL::NonNullType.new(of_type: to_query_type(ast_type.of_type, types))
59
- elsif ast_type.is_a?(GraphQL::Language::Nodes::ListType)
60
- GraphQL::ListType.new(of_type: to_query_type(ast_type.of_type, types))
59
+ def to_query_type(ast_type, warden)
60
+ case ast_type
61
+ when GraphQL::Language::Nodes::NonNullType
62
+ wrap_query_type(to_query_type(ast_type.of_type, warden), GraphQL::NonNullType)
63
+ when GraphQL::Language::Nodes::ListType
64
+ wrap_query_type(to_query_type(ast_type.of_type, warden), GraphQL::ListType)
65
+ else
66
+ warden.get_type(ast_type.name)
67
+ end
68
+ end
69
+
70
+ def wrap_query_type(type, wrapper)
71
+ if type.nil?
72
+ nil
61
73
  else
62
- types[ast_type.name]
74
+ wrapper.new(of_type: type)
63
75
  end
64
76
  end
65
77
 
@@ -12,7 +12,11 @@ module GraphQL
12
12
  # It also provides limited access to the {TypeStack} instance,
13
13
  # which tracks state as you climb in and out of different fields.
14
14
  class ValidationContext
15
- attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations, :warden
15
+ attr_reader :query, :schema,
16
+ :document, :errors, :visitor,
17
+ :fragments, :operations, :warden,
18
+ :dependencies, :each_irep_node_handlers
19
+
16
20
  def initialize(query)
17
21
  @query = query
18
22
  @schema = query.schema
@@ -33,12 +37,29 @@ module GraphQL
33
37
  @errors = []
34
38
  @visitor = GraphQL::Language::Visitor.new(document)
35
39
  @type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
40
+ definition_dependencies = DefinitionDependencies.mount(self)
41
+ @on_dependency_resolve_handlers = []
42
+ @each_irep_node_handlers = []
43
+ visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) {
44
+ @dependencies = definition_dependencies.dependency_map { |defn, spreads, frag|
45
+ @on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) }
46
+ }
47
+ }
48
+ end
49
+
50
+
51
+ def on_dependency_resolve(&handler)
52
+ @on_dependency_resolve_handlers << handler
36
53
  end
37
54
 
38
55
  def object_types
39
56
  @type_stack.object_types
40
57
  end
41
58
 
59
+ def each_irep_node(&handler)
60
+ @each_irep_node_handlers << handler
61
+ end
62
+
42
63
  # @return [GraphQL::BaseType] The current object type
43
64
  def type_definition
44
65
  object_types.last
@@ -32,11 +32,14 @@ module GraphQL
32
32
  end
33
33
 
34
34
  context.visitor.visit
35
+ # Post-validation: allow validators to register handlers on rewritten query nodes
36
+ rewrite_result = rewrite.operations
37
+ GraphQL::InternalRepresentation::Visit.visit_each_node(rewrite_result, context.each_irep_node_handlers)
35
38
 
36
39
  {
37
40
  errors: context.errors,
38
41
  # If there were errors, the irep is garbage
39
- irep: context.errors.none? ? rewrite.operations : nil,
42
+ irep: context.errors.any? ? nil : rewrite_result,
40
43
  }
41
44
  end
42
45
  end
@@ -3,7 +3,11 @@ GraphQL::STRING_TYPE = GraphQL::ScalarType.define do
3
3
  name "String"
4
4
  description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text."
5
5
 
6
- coerce_result ->(value) { value.to_s }
6
+ coerce_result ->(value) {
7
+ str = value.to_s
8
+ str.encoding == Encoding::US_ASCII || str.encoding == Encoding::UTF_8 ? str : nil
9
+ }
10
+
7
11
  coerce_input ->(value) { value.is_a?(String) ? value : nil }
8
12
  default_scalar true
9
13
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.4.5"
3
+ VERSION = "1.5.3"
4
4
  end
data/readme.md CHANGED
@@ -8,8 +8,9 @@
8
8
 
9
9
  A Ruby implementation of [GraphQL](http://graphql.org/).
10
10
 
11
- - [Website](https://rmosolgo.github.io/graphql-ruby)
12
- - [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
11
+ - [Website](https://rmosolgo.github.io/graphql-ruby)
12
+ - [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
13
+ - [Newsletter](https://tinyletter.com/graphql-ruby)
13
14
 
14
15
  ## Installation
15
16
 
@@ -26,7 +27,15 @@ $ bundle install
26
27
 
27
28
  ## Getting Started
28
29
 
29
- See "Getting Started" on the [website](https://rmosolgo.github.io/graphql-ruby/) or on [GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/index.md)
30
+ ```
31
+ $ rails generate graphql:install
32
+ ```
33
+
34
+ Or, see ["Getting Started"](https://rmosolgo.github.io/graphql-ruby/).
35
+
36
+ ## Upgrade
37
+
38
+ 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 [static queries](http://rmosolgo.github.io/graphql-ruby/pro/persisted_queries). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development!
30
39
 
31
40
  ## Goals
32
41
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/enum_generator"
4
+
5
+ class GraphQLGeneratorsEnumGeneratorTest < BaseGeneratorTest
6
+ tests Graphql::Generators::EnumGenerator
7
+
8
+ test "it generate enums with values" do
9
+ expected_content = <<-RUBY
10
+ Types::FamilyType = GraphQL::EnumType.define do
11
+ name "Family"
12
+ value "NIGHTSHADE"
13
+ value "BRASSICA", value: Family::COLE
14
+ value "UMBELLIFER", value: :umbellifer
15
+ value "LEGUME", value: "bean & friends"
16
+ value "CURCURBITS", value: 5
17
+ end
18
+ RUBY
19
+
20
+ run_generator(["Family",
21
+ "NIGHTSHADE",
22
+ "BRASSICA:Family::COLE",
23
+ "UMBELLIFER::umbellifer",
24
+ 'LEGUME:"bean & friends"',
25
+ "CURCURBITS:5"
26
+ ])
27
+ assert_file "app/graphql/types/family_type.rb", expected_content
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/function_generator"
4
+
5
+ class GraphQLGeneratorsFunctionGeneratorTest < BaseGeneratorTest
6
+ tests Graphql::Generators::FunctionGenerator
7
+
8
+ test "it generates an empty function by name" do
9
+ run_generator(["FindRecord"])
10
+
11
+ expected_content = <<-RUBY
12
+ class Functions::FindRecord < GraphQL::Function
13
+ # Define `initialize` to store field-level options, eg
14
+ #
15
+ # field :myField, function: Functions::FindRecord.new(type: MyType)
16
+ #
17
+ # attr_reader :type
18
+ # def initialize(type:)
19
+ # @type = type
20
+ # end
21
+
22
+ # add arguments by type:
23
+ # argument :id, !GraphQL::ID_TYPE
24
+
25
+ # Resolve function:
26
+ def call(obj, args, ctx)
27
+ end
28
+ end
29
+ RUBY
30
+
31
+ assert_file "app/graphql/functions/find_record.rb", expected_content
32
+ end
33
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/install_generator"
4
+
5
+ class GraphQLGeneratorsInstallGeneratorTest < Rails::Generators::TestCase
6
+
7
+ tests Graphql::Generators::InstallGenerator
8
+ destination File.expand_path("../../../tmp/dummy", File.dirname(__FILE__))
9
+
10
+ setup do
11
+ prepare_destination
12
+ FileUtils.cd(File.expand_path("../../../tmp", File.dirname(__FILE__))) do
13
+ `rm -rf dummy`
14
+ `rails new dummy --skip-active-record --skip-test-unit --skip-spring --skip-bundle`
15
+ end
16
+ end
17
+
18
+ test "it generates a folder structure" do
19
+ run_generator([])
20
+
21
+ assert_file "app/graphql/types/.keep"
22
+ assert_file "app/graphql/mutations/.keep"
23
+ expected_query_route = %|post "/graphql", to: "graphql#execute"|
24
+ expected_graphiql_route = %|
25
+ if Rails.env.development?
26
+ mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
27
+ end
28
+ |
29
+
30
+ assert_file "config/routes.rb" do |contents|
31
+ assert_includes contents, expected_query_route
32
+ assert_includes contents, expected_graphiql_route
33
+ end
34
+
35
+ assert_file "Gemfile" do |contents|
36
+ assert_match %r{gem ('|")graphiql-rails('|"), :?group(:| =>) :development}, contents
37
+ end
38
+
39
+ expected_schema = <<-RUBY
40
+ DummySchema = GraphQL::Schema.define do
41
+ query(Types::QueryType)
42
+ end
43
+ RUBY
44
+ assert_file "app/graphql/dummy_schema.rb", expected_schema
45
+
46
+
47
+ expected_query_type = <<-RUBY
48
+ Types::QueryType = GraphQL::ObjectType.define do
49
+ name "Query"
50
+ # Add root-level fields here.
51
+ # They will be entry points for queries on your schema.
52
+
53
+ # TODO: remove me
54
+ field :testField, types.String do
55
+ description \"An example field added by the generator\"
56
+ resolve ->(obj, args, ctx) {
57
+ \"Hello World!\"
58
+ }
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ assert_file "app/graphql/types/query_type.rb", expected_query_type
64
+ assert_file "app/controllers/graphql_controller.rb", EXPECTED_GRAPHQLS_CONTROLLER
65
+ end
66
+
67
+ test "it generates graphql-batch and relay boilerplate" do
68
+ run_generator(["--batch", "--relay"])
69
+ assert_file "app/graphql/loaders/.keep"
70
+ assert_file "Gemfile" do |contents|
71
+ assert_match %r{gem ('|")graphql-batch('|")}, contents
72
+ end
73
+
74
+ expected_query_type = <<-RUBY
75
+ Types::QueryType = GraphQL::ObjectType.define do
76
+ name "Query"
77
+ # Add root-level fields here.
78
+ # They will be entry points for queries on your schema.
79
+
80
+ # TODO: remove me
81
+ field :testField, types.String do
82
+ description \"An example field added by the generator\"
83
+ resolve ->(obj, args, ctx) {
84
+ \"Hello World!\"
85
+ }
86
+ end
87
+
88
+ field :node, GraphQL::Relay::Node.field
89
+ end
90
+ RUBY
91
+
92
+ assert_file "app/graphql/types/query_type.rb", expected_query_type
93
+ assert_file "app/graphql/dummy_schema.rb", EXPECTED_RELAY_BATCH_SCHEMA
94
+ end
95
+
96
+ test "it can skip keeps, skip graphiql and customize schema name" do
97
+ run_generator(["--skip-keeps", "--skip-graphiql", "--schema=CustomSchema"])
98
+ assert_no_file "app/graphql/types/.keep"
99
+ assert_no_file "app/graphql/mutations/.keep"
100
+ assert_file "app/graphql/types"
101
+ assert_file "app/graphql/mutations"
102
+ assert_file "Gemfile" do |contents|
103
+ refute_includes contents, "graphiql-rails"
104
+ end
105
+
106
+ assert_file "config/routes.rb" do |contents|
107
+ refute_includes contents, "GraphiQL::Rails"
108
+ end
109
+
110
+ assert_file "app/graphql/custom_schema.rb", /CustomSchema = GraphQL::Schema\.define/
111
+ assert_file "app/controllers/graphql_controller.rb", /CustomSchema\.execute/
112
+ end
113
+
114
+ EXPECTED_GRAPHQLS_CONTROLLER = <<-RUBY
115
+ class GraphqlController < ApplicationController
116
+ def execute
117
+ variables = ensure_hash(params[:variables])
118
+ query = params[:query]
119
+ context = {
120
+ # Query context goes here, for example:
121
+ # current_user: current_user,
122
+ }
123
+ result = DummySchema.execute(query, variables: variables, context: context)
124
+ render json: result
125
+ end
126
+
127
+ private
128
+
129
+ # Handle form data, JSON body, or a blank value
130
+ def ensure_hash(ambiguous_param)
131
+ case ambiguous_param
132
+ when String
133
+ if ambiguous_param.present?
134
+ ensure_hash(JSON.parse(ambiguous_param))
135
+ else
136
+ {}
137
+ end
138
+ when Hash, ActionController::Parameters
139
+ ambiguous_param
140
+ when nil
141
+ {}
142
+ else
143
+ raise ArgumentError, "Unexpected parameter: \#{ambiguous_param}"
144
+ end
145
+ end
146
+ end
147
+ RUBY
148
+
149
+ EXPECTED_RELAY_BATCH_SCHEMA = <<-RUBY
150
+ DummySchema = GraphQL::Schema.define do
151
+ query(Types::QueryType)
152
+
153
+ # Relay Object Identification:
154
+
155
+ # Return a string UUID for `object`
156
+ id_from_object ->(object, type_definition, query_ctx) {
157
+ # Here's a simple implementation which:
158
+ # - joins the type name & object.id
159
+ # - encodes it with base64:
160
+ # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
161
+ }
162
+
163
+ # Given a string UUID, find the object
164
+ object_from_id ->(id, query_ctx) {
165
+ # For example, to decode the UUIDs generated above:
166
+ # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
167
+ #
168
+ # Then, based on `type_name` and `id`
169
+ # find an object in your application
170
+ # ...
171
+ }
172
+
173
+ # Object Resolution
174
+ resolve_type -> (obj, ctx) {
175
+ # TODO: Implement this function
176
+ # to return the correct type for `obj`
177
+ raise(NotImplementedError)
178
+ }
179
+
180
+ # GraphQL::Batch setup:
181
+ lazy_resolve(Promise, :sync)
182
+ instrument(:query, GraphQL::Batch::Setup)
183
+ end
184
+ RUBY
185
+ end