graphql 1.4.5 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'generators/graphql/type_generator'
3
+
4
+ module Graphql
5
+ module Generators
6
+ # Generate a union type by name
7
+ # with the specified member types.
8
+ #
9
+ # ```
10
+ # rails g graphql:union SearchResultType ImageType AudioType
11
+ # ```
12
+ class UnionGenerator < TypeGeneratorBase
13
+ desc "Create a GraphQL::UnionType with the given name and possible types"
14
+ source_root File.expand_path('../templates', __FILE__)
15
+
16
+ argument :possible_types,
17
+ type: :array,
18
+ default: [],
19
+ banner: "type type ...",
20
+ desc: "Possible types for this union (expressed as Ruby or GraphQL)"
21
+
22
+ def create_type_file
23
+ template "union.erb", "app/graphql/types/#{type_file_name}.rb"
24
+ end
25
+
26
+ private
27
+
28
+ def normalized_possible_types
29
+ possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby) }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "delegate"
2
3
  require "json"
3
4
  require "set"
4
5
  require "singleton"
@@ -16,6 +17,14 @@ module GraphQL
16
17
  @col = col
17
18
  @query = query
18
19
  end
20
+
21
+ def to_h
22
+ locations = line ? [{ "line" => line, "column" => col }] : []
23
+ {
24
+ "message" => message,
25
+ "locations" => locations,
26
+ }
27
+ end
19
28
  end
20
29
 
21
30
  # Turn a query string into an AST
@@ -85,3 +94,4 @@ require "graphql/static_validation"
85
94
  require "graphql/version"
86
95
  require "graphql/relay"
87
96
  require "graphql/compatibility"
97
+ require "graphql/function"
@@ -22,7 +22,7 @@ module GraphQL
22
22
  reduce_node(op_node, reducer_states)
23
23
  end
24
24
 
25
- reducer_states.map { |r| r.finalize_reducer }
25
+ reducer_states.map(&:finalize_reducer)
26
26
  end
27
27
 
28
28
  private
@@ -19,13 +19,10 @@ module GraphQL
19
19
  # State for the query complexity calcuation:
20
20
  # - `query` is needed for variables, then passed to handler
21
21
  # - `complexities_on_type` holds complexity scores for each type in an IRep node
22
- # - `skip_depth` increments for each skipped node, then decrements on the way out.
23
- # While it's greater than `0`, we're visiting a skipped part of the query.
24
22
  def initial_value(query)
25
23
  {
26
24
  query: query,
27
25
  complexities_on_type: [TypeComplexity.new],
28
- skip_depth: 0,
29
26
  }
30
27
  end
31
28
 
@@ -33,22 +30,12 @@ module GraphQL
33
30
  def call(memo, visit_type, irep_node)
34
31
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
35
32
  if visit_type == :enter
36
- if irep_node.skipped?
37
- memo[:skip_depth] += 1
38
- elsif memo[:skip_depth] == 0
39
- memo[:complexities_on_type].push(TypeComplexity.new)
40
- end
33
+ memo[:complexities_on_type].push(TypeComplexity.new)
41
34
  else
42
- if memo[:skip_depth] > 0
43
- if irep_node.skipped?
44
- memo[:skip_depth] -= 1
45
- end
46
- else
47
- type_complexities = memo[:complexities_on_type].pop
48
- child_complexity = type_complexities.max_possible_complexity
49
- own_complexity = get_complexity(irep_node, memo[:query], child_complexity)
50
- memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity)
51
- end
35
+ type_complexities = memo[:complexities_on_type].pop
36
+ child_complexity = type_complexities.max_possible_complexity
37
+ own_complexity = get_complexity(irep_node, memo[:query], child_complexity)
38
+ memo[:complexities_on_type].last.merge(irep_node.owner_type, own_complexity)
52
39
  end
53
40
  end
54
41
  memo
@@ -89,22 +76,7 @@ module GraphQL
89
76
 
90
77
  # Return the max possible complexity for types in this selection
91
78
  def max_possible_complexity
92
- max_complexity = 0
93
-
94
- @types.each do |type_defn, own_complexity|
95
- type_complexity = @types.reduce(0) do |memo, (other_type, other_complexity)|
96
- if types_overlap?(type_defn, other_type)
97
- memo + other_complexity
98
- else
99
- memo
100
- end
101
- end
102
-
103
- if type_complexity > max_complexity
104
- max_complexity = type_complexity
105
- end
106
- end
107
- max_complexity
79
+ @types.each_value.max || 0
108
80
  end
109
81
 
110
82
  # Store the complexity for the branch on `type_defn`.
@@ -112,22 +84,6 @@ module GraphQL
112
84
  def merge(type_defn, complexity)
113
85
  @types[type_defn] += complexity
114
86
  end
115
-
116
- private
117
- # True if:
118
- # - type_1 is type_2
119
- # - type_1 is a member of type_2's possible types
120
- def types_overlap?(type_1, type_2)
121
- if type_1 == type_2
122
- true
123
- elsif type_2.kind.union?
124
- type_2.include?(type_1)
125
- elsif type_1.kind.object? && type_2.kind.interface?
126
- type_1.interfaces.include?(type_2)
127
- else
128
- false
129
- end
130
- end
131
87
  end
132
88
  end
133
89
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  def call(memo, visit_type, irep_node)
26
26
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
27
27
  # Don't validate introspection fields or skipped nodes
28
- not_validated_node = GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name) || !irep_node.included?
28
+ not_validated_node = GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name)
29
29
  if visit_type == :enter
30
30
  if not_validated_node
31
31
  memo[:skip_depth] += 1
@@ -53,5 +53,26 @@ module GraphQL
53
53
  def expose_as
54
54
  @expose_as ||= (@as || @name).to_s
55
55
  end
56
+
57
+ NO_DEFAULT_VALUE = Object.new
58
+ # @api private
59
+ def self.from_dsl(name, type = nil, description = nil, default_value: NO_DEFAULT_VALUE, as: nil, &block)
60
+ argument = if block_given?
61
+ GraphQL::Argument.define(&block)
62
+ else
63
+ GraphQL::Argument.new
64
+ end
65
+
66
+ argument.name = name.to_s
67
+ type && argument.type = type
68
+ description && argument.description = description
69
+ if default_value != NO_DEFAULT_VALUE
70
+ argument.default_value = default_value
71
+ end
72
+ argument.as = as
73
+
74
+
75
+ argument
76
+ end
56
77
  end
57
78
  end
@@ -10,7 +10,7 @@ module GraphQL
10
10
  has_count_interface = GraphQL::InterfaceType.define do
11
11
  name "HasCount"
12
12
  field :count, types.Int
13
- field :counter, ->{ counter_type }
13
+ field :counter, ->{ has_count_interface }
14
14
  end
15
15
 
16
16
  counter_type = GraphQL::ObjectType.define do
@@ -29,7 +29,7 @@ module GraphQL
29
29
 
30
30
  has_counter_interface = GraphQL::InterfaceType.define do
31
31
  name "HasCounter"
32
- field :counter, counter_type
32
+ field :counter, has_count_interface
33
33
  end
34
34
 
35
35
  query_type = GraphQL::ObjectType.define do
@@ -41,7 +41,7 @@ module GraphQL
41
41
  schema = GraphQL::Schema.define(
42
42
  query: query_type,
43
43
  resolve_type: ->(o, c) { o == :counter ? counter_type : nil },
44
- orphan_types: [alt_counter_type],
44
+ orphan_types: [alt_counter_type, counter_type],
45
45
  query_execution_strategy: execution_strategy,
46
46
  )
47
47
  schema.metadata[:count] = 0
@@ -3,6 +3,7 @@ require "graphql/define/assign_argument"
3
3
  require "graphql/define/assign_connection"
4
4
  require "graphql/define/assign_enum_value"
5
5
  require "graphql/define/assign_global_id_field"
6
+ require "graphql/define/assign_mutation_function"
6
7
  require "graphql/define/assign_object_field"
7
8
  require "graphql/define/defined_object_proxy"
8
9
  require "graphql/define/instance_definable"
@@ -3,25 +3,9 @@ module GraphQL
3
3
  module Define
4
4
  # Turn argument configs into a {GraphQL::Argument}.
5
5
  module AssignArgument
6
- def self.call(target, name, type = nil, description = nil, **rest, &block)
7
- argument = if block_given?
8
- GraphQL::Argument.define(&block)
9
- else
10
- GraphQL::Argument.new
11
- end
12
-
13
- unsupported_keys = rest.keys - [:default_value, :as]
14
- if unsupported_keys.any?
15
- raise ArgumentError.new("unknown keyword#{unsupported_keys.length > 1 ? 's' : ''}: #{unsupported_keys.join(', ')}")
16
- end
17
-
18
- argument.name = name.to_s
19
- type && argument.type = type
20
- description && argument.description = description
21
- rest.key?(:default_value) && argument.default_value = rest[:default_value]
22
- argument.as = rest[:as]
23
-
24
- target.arguments[name.to_s] = argument
6
+ def self.call(target, *args, **kwargs, &block)
7
+ argument = GraphQL::Argument.from_dsl(*args, **kwargs, &block)
8
+ target.arguments[argument.name] = argument
25
9
  end
26
10
  end
27
11
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Define
4
+ module AssignMutationFunction
5
+ def self.call(target, function)
6
+ # TODO: get all this logic somewhere easier to test
7
+
8
+ if !function.type.is_a?(GraphQL::ObjectType)
9
+ raise "Mutation functions must return object types (not #{function.type.unwrap})"
10
+ end
11
+
12
+ target.return_type = function.type.redefine {
13
+ name(target.name + "Payload")
14
+ field :clientMutationId, types.String, "A unique identifier for the client performing the mutation.", property: :client_mutation_id
15
+ }
16
+
17
+ target.arguments = function.arguments
18
+ target.description = function.description
19
+ target.resolve = ->(o, a, c) {
20
+ res = function.call(o, a, c)
21
+ ResultProxy.new(res, a[:clientMutationId])
22
+ }
23
+ end
24
+
25
+ class ResultProxy < SimpleDelegator
26
+ attr_reader :client_mutation_id
27
+ def initialize(target, client_mutation_id)
28
+ @client_mutation_id = client_mutation_id
29
+ super(target)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -3,31 +3,43 @@ module GraphQL
3
3
  module Define
4
4
  # Turn field configs into a {GraphQL::Field} and attach it to a {GraphQL::ObjectType} or {GraphQL::InterfaceType}
5
5
  module AssignObjectField
6
- def self.call(owner_type, name, type_or_field = nil, desc = nil, field: nil, **kwargs, &block)
6
+ def self.call(owner_type, name, type_or_field = nil, desc = nil, function: nil, field: nil, relay_mutation_function: nil, **kwargs, &block)
7
7
  name_s = name.to_s
8
8
 
9
- # Move some possitional definitions into keyword defns:
10
- kwargs[:description] ||= desc
11
- kwargs[:name] ||= name_s
9
+ # Move some positional args into keywords if they're present
10
+ desc && kwargs[:description] ||= desc
11
+ name && kwargs[:name] ||= name_s
12
12
 
13
13
  if !type_or_field.nil? && !type_or_field.is_a?(GraphQL::Field)
14
+ # Maybe a string, proc or BaseType
14
15
  kwargs[:type] = type_or_field
15
16
  end
16
17
 
17
- # Figure out how to find or initialize the field instance:
18
- if type_or_field.is_a?(GraphQL::Field)
19
- field = type_or_field
20
- field = field.redefine(name: name_s)
21
- elsif block_given?
22
- field = GraphQL::Field.define(kwargs, &block)
23
- elsif field.nil?
24
- field = GraphQL::Field.define(kwargs)
18
+ base_field = if type_or_field.is_a?(GraphQL::Field)
19
+ type_or_field.redefine(name: name_s)
20
+ elsif function
21
+ GraphQL::Field.define(
22
+ arguments: function.arguments,
23
+ complexity: function.complexity,
24
+ name: name_s,
25
+ type: function.type,
26
+ resolve: function,
27
+ description: function.description,
28
+ deprecation_reason: function.deprecation_reason,
29
+ )
25
30
  elsif field.is_a?(GraphQL::Field)
26
- field = field.redefine(name: name_s)
31
+ field.redefine(name: name_s)
27
32
  else
28
- raise("Couldn't find a field argument, received: #{field || type_or_field}")
33
+ nil
29
34
  end
30
35
 
36
+ field = if base_field
37
+ base_field.redefine(kwargs, &block)
38
+ else
39
+ GraphQL::Field.define(kwargs, &block)
40
+ end
41
+
42
+
31
43
  # Attach the field to the type
32
44
  owner_type.fields[name_s] = field
33
45
  end
@@ -1,16 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Define
4
+ # This object delegates most methods to a dictionary of functions, {@dictionary}.
5
+ # {@target} is passed to the specified function, along with any arguments and block.
6
+ # This allows a method-based DSL without adding methods to the defined class.
4
7
  class DefinedObjectProxy
8
+ # The object which will be defined by definition functions
9
+ attr_reader :target
10
+
5
11
  def initialize(target)
6
12
  @target = target
7
13
  @dictionary = target.class.dictionary
8
14
  end
9
15
 
16
+ # Provides shorthand access to GraphQL's built-in types
10
17
  def types
11
18
  GraphQL::Define::TypeDefiner.instance
12
19
  end
13
20
 
21
+ # Allow `plugin` to perform complex initialization on the definition.
22
+ # Calls `plugin.use(defn, **kwargs)`.
23
+ # @param plugin [<#use(defn, **kwargs)>] A plugin object
24
+ # @param kwargs [Hash] Any options for the plugin
25
+ def use(plugin, **kwargs)
26
+ # https://bugs.ruby-lang.org/issues/10708
27
+ if kwargs == {}
28
+ plugin.use(self)
29
+ else
30
+ plugin.use(self, **kwargs)
31
+ end
32
+ end
33
+
34
+ # Lookup a function from the dictionary and call it if it's found.
14
35
  def method_missing(name, *args, &block)
15
36
  definition = @dictionary[name]
16
37
  if definition
@@ -21,19 +21,23 @@ module GraphQL
21
21
  #
22
22
  # @example Make a class definable
23
23
  # class Car
24
- # attr_accessor :make, :model
24
+ # include GraphQL::Define::InstanceDefinable
25
+ # attr_accessor :make, :model, :doors
25
26
  # accepts_definitions(
26
27
  # # These attrs will be defined with plain setters, `{attr}=`
27
28
  # :make, :model,
28
29
  # # This attr has a custom definition which applies the config to the target
29
30
  # doors: ->(car, doors_count) { doors_count.times { car.doors << Door.new } }
30
31
  # )
32
+ # ensure_defined(:make, :model, :doors)
31
33
  #
32
34
  # def initialize
33
35
  # @doors = []
34
36
  # end
35
37
  # end
36
38
  #
39
+ # class Door; end;
40
+ #
37
41
  # # Create an instance with `.define`:
38
42
  # subaru_baja = Car.define do
39
43
  # make "Subaru"
@@ -57,6 +61,27 @@ module GraphQL
57
61
  # # Access it from metadata
58
62
  # subaru_baja.metadata[:all_wheel_drive] # => true
59
63
  #
64
+ # @example Extending the definition of a class via a plugin
65
+ # # A plugin is any object that responds to `.use(definition)`
66
+ # module SubaruCar
67
+ # extend self
68
+ #
69
+ # def use(defn)
70
+ # # `defn` has the same methods as within `.define { ... }` block
71
+ # defn.make "Subaru"
72
+ # defn.doors 4
73
+ # end
74
+ # end
75
+ #
76
+ # # Use the plugin within a `.define { ... }` block
77
+ # subaru_baja = Car.define do
78
+ # use SubaruCar
79
+ # model 'Baja'
80
+ # end
81
+ #
82
+ # subaru_baja.make # => "Subaru"
83
+ # subaru_baja.doors # => [<Door>, <Door>, <Door>, <Door>]
84
+ #
60
85
  # @example Making a copy with an extended definition
61
86
  # # Create an instance with `.define`:
62
87
  # subaru_baja = Car.define do
@@ -91,6 +116,18 @@ module GraphQL
91
116
  def define(**kwargs, &block)
92
117
  # make sure the previous definition_proc was executed:
93
118
  ensure_defined
119
+
120
+ method_names = self.class.ensure_defined_method_names
121
+ @pending_methods = method_names.map { |n| self.class.instance_method(n) }
122
+ self.singleton_class.class_eval do
123
+ method_names.each do |method_name|
124
+ define_method(method_name) { |*args, &block|
125
+ ensure_defined
126
+ self.send(method_name, *args, &block)
127
+ }
128
+ end
129
+ end
130
+
94
131
  @pending_definition = Definition.new(kwargs, block)
95
132
  nil
96
133
  end
@@ -119,8 +156,18 @@ module GraphQL
119
156
  # @return [void]
120
157
  def ensure_defined
121
158
  if @pending_definition
159
+
122
160
  defn = @pending_definition
123
161
  @pending_definition = nil
162
+
163
+ pending_methods = @pending_methods
164
+ self.singleton_class.class_eval {
165
+ pending_methods.each do |method|
166
+ define_method(method.name, method)
167
+ end
168
+ }
169
+ @pending_methods = nil
170
+
124
171
  defn_proxy = DefinedObjectProxy.new(self)
125
172
  # Apply definition from `define(...)` kwargs
126
173
  defn.define_keywords.each do |keyword, value|
@@ -130,6 +177,8 @@ module GraphQL
130
177
  if defn.define_proc
131
178
  defn_proxy.instance_eval(&defn.define_proc)
132
179
  end
180
+
181
+
133
182
  end
134
183
  nil
135
184
  end
@@ -171,19 +220,20 @@ module GraphQL
171
220
  end
172
221
 
173
222
  def ensure_defined(*method_names)
174
- ensure_defined_module = Module.new
175
- ensure_defined_module.module_eval {
176
- method_names.each do |method_name|
177
- define_method(method_name) { |*args, &block|
178
- ensure_defined
179
- super(*args, &block)
180
- }
181
- end
182
- }
183
- self.prepend(ensure_defined_module)
223
+ @ensure_defined_method_names ||= []
224
+ @ensure_defined_method_names.concat(method_names)
184
225
  nil
185
226
  end
186
227
 
228
+ def ensure_defined_method_names
229
+ own_method_names = @ensure_defined_method_names || []
230
+ if superclass.respond_to?(:ensure_defined_method_names)
231
+ superclass.ensure_defined_method_names + own_method_names
232
+ else
233
+ own_method_names
234
+ end
235
+ end
236
+
187
237
  # @return [Hash] combined definitions for self and ancestors
188
238
  def dictionary
189
239
  if superclass.respond_to?(:dictionary)