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
@@ -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)