graphql 1.9.17 → 1.11.7
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/generators/graphql/core.rb +18 -2
- data/lib/generators/graphql/install_generator.rb +27 -0
- data/lib/generators/graphql/object_generator.rb +52 -8
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/enum.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +14 -10
- data/lib/generators/graphql/templates/interface.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/object.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/scalar.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +10 -0
- data/lib/generators/graphql/templates/union.erb +3 -1
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +178 -67
- data/lib/graphql/analysis/ast/visitor.rb +3 -3
- data/lib/graphql/analysis/ast.rb +12 -11
- data/lib/graphql/argument.rb +10 -38
- data/lib/graphql/backtrace/table.rb +10 -2
- data/lib/graphql/backtrace/tracer.rb +2 -1
- data/lib/graphql/base_type.rb +4 -0
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
- data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
- data/lib/graphql/define/assign_enum_value.rb +1 -1
- data/lib/graphql/define/assign_global_id_field.rb +2 -2
- data/lib/graphql/define/assign_object_field.rb +3 -3
- data/lib/graphql/define/defined_object_proxy.rb +3 -0
- data/lib/graphql/define/instance_definable.rb +18 -108
- data/lib/graphql/directive/deprecated_directive.rb +1 -12
- data/lib/graphql/directive.rb +8 -1
- data/lib/graphql/enum_type.rb +5 -71
- data/lib/graphql/execution/directive_checks.rb +2 -2
- data/lib/graphql/execution/errors.rb +2 -3
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/instrumentation.rb +1 -1
- data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
- data/lib/graphql/execution/interpreter/arguments.rb +51 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +79 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +25 -0
- data/lib/graphql/execution/interpreter/runtime.rb +227 -254
- data/lib/graphql/execution/interpreter.rb +34 -11
- data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
- data/lib/graphql/execution/lookahead.rb +39 -114
- data/lib/graphql/execution/multiplex.rb +14 -5
- data/lib/graphql/field.rb +14 -118
- data/lib/graphql/filter.rb +1 -1
- data/lib/graphql/function.rb +1 -30
- data/lib/graphql/input_object_type.rb +6 -24
- data/lib/graphql/integer_decoding_error.rb +17 -0
- data/lib/graphql/interface_type.rb +7 -23
- data/lib/graphql/internal_representation/scope.rb +2 -2
- data/lib/graphql/internal_representation/visit.rb +2 -2
- data/lib/graphql/introspection/base_object.rb +2 -5
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +7 -7
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +33 -9
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/schema_type.rb +4 -9
- data/lib/graphql/introspection/type_type.rb +11 -7
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/invalid_null_error.rb +18 -0
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/definition_slice.rb +21 -10
- data/lib/graphql/language/document_from_schema_definition.rb +89 -64
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +52 -91
- data/lib/graphql/language/parser.rb +719 -717
- data/lib/graphql/language/parser.y +104 -98
- data/lib/graphql/language/printer.rb +1 -1
- data/lib/graphql/language/sanitized_printer.rb +222 -0
- data/lib/graphql/language/visitor.rb +2 -2
- data/lib/graphql/language.rb +2 -1
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/non_null_type.rb +0 -10
- data/lib/graphql/object_type.rb +45 -56
- data/lib/graphql/pagination/active_record_relation_connection.rb +41 -0
- data/lib/graphql/pagination/array_connection.rb +77 -0
- data/lib/graphql/pagination/connection.rb +208 -0
- data/lib/graphql/pagination/connections.rb +145 -0
- data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
- data/lib/graphql/pagination/relation_connection.rb +185 -0
- data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
- data/lib/graphql/pagination.rb +6 -0
- data/lib/graphql/query/arguments.rb +4 -2
- data/lib/graphql/query/context.rb +36 -9
- data/lib/graphql/query/fingerprint.rb +26 -0
- data/lib/graphql/query/input_validation_result.rb +23 -6
- data/lib/graphql/query/literal_input.rb +30 -10
- data/lib/graphql/query/null_context.rb +5 -1
- data/lib/graphql/query/validation_pipeline.rb +4 -1
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query/variables.rb +16 -7
- data/lib/graphql/query.rb +64 -15
- data/lib/graphql/rake_task/validate.rb +3 -0
- data/lib/graphql/rake_task.rb +9 -9
- data/lib/graphql/relay/array_connection.rb +10 -12
- data/lib/graphql/relay/base_connection.rb +23 -13
- data/lib/graphql/relay/connection_type.rb +2 -1
- data/lib/graphql/relay/edge_type.rb +1 -0
- data/lib/graphql/relay/edges_instrumentation.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -86
- data/lib/graphql/relay/node.rb +2 -2
- data/lib/graphql/relay/range_add.rb +14 -5
- data/lib/graphql/relay/relation_connection.rb +8 -10
- data/lib/graphql/scalar_type.rb +15 -59
- data/lib/graphql/schema/argument.rb +113 -11
- data/lib/graphql/schema/base_64_encoder.rb +2 -0
- data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
- data/lib/graphql/schema/build_from_definition.rb +212 -190
- data/lib/graphql/schema/built_in_types.rb +5 -5
- data/lib/graphql/schema/default_type_error.rb +2 -0
- data/lib/graphql/schema/directive/deprecated.rb +18 -0
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive.rb +34 -3
- data/lib/graphql/schema/enum.rb +52 -4
- data/lib/graphql/schema/enum_value.rb +6 -1
- data/lib/graphql/schema/field/connection_extension.rb +44 -20
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +200 -129
- data/lib/graphql/schema/find_inherited_value.rb +13 -0
- data/lib/graphql/schema/finder.rb +13 -11
- data/lib/graphql/schema/input_object.rb +131 -22
- data/lib/graphql/schema/interface.rb +26 -8
- data/lib/graphql/schema/introspection_system.rb +108 -37
- data/lib/graphql/schema/late_bound_type.rb +3 -2
- data/lib/graphql/schema/list.rb +47 -0
- data/lib/graphql/schema/loader.rb +134 -96
- data/lib/graphql/schema/member/base_dsl_methods.rb +29 -12
- data/lib/graphql/schema/member/build_type.rb +19 -5
- data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
- data/lib/graphql/schema/member/has_arguments.rb +105 -5
- data/lib/graphql/schema/member/has_ast_node.rb +20 -0
- data/lib/graphql/schema/member/has_fields.rb +20 -10
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
- data/lib/graphql/schema/member/validates_input.rb +33 -0
- data/lib/graphql/schema/member.rb +6 -0
- data/lib/graphql/schema/mutation.rb +5 -1
- data/lib/graphql/schema/non_null.rb +30 -0
- data/lib/graphql/schema/object.rb +65 -12
- data/lib/graphql/schema/possible_types.rb +9 -4
- data/lib/graphql/schema/printer.rb +0 -15
- data/lib/graphql/schema/relay_classic_mutation.rb +5 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +5 -2
- data/lib/graphql/schema/resolver.rb +26 -18
- data/lib/graphql/schema/scalar.rb +27 -3
- data/lib/graphql/schema/subscription.rb +8 -18
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +21 -13
- data/lib/graphql/schema/type_membership.rb +2 -2
- data/lib/graphql/schema/union.rb +37 -3
- data/lib/graphql/schema/unique_within_type.rb +1 -2
- data/lib/graphql/schema/validation.rb +10 -2
- data/lib/graphql/schema/warden.rb +115 -29
- data/lib/graphql/schema.rb +903 -195
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +10 -6
- data/lib/graphql/static_validation/literal_validator.rb +52 -27
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +43 -83
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +17 -5
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +33 -25
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
- data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -5
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
- data/lib/graphql/static_validation/type_stack.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
- data/lib/graphql/static_validation/validator.rb +30 -8
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +89 -19
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
- data/lib/graphql/subscriptions/event.rb +23 -5
- data/lib/graphql/subscriptions/instrumentation.rb +10 -5
- data/lib/graphql/subscriptions/serialize.rb +22 -4
- data/lib/graphql/subscriptions/subscription_root.rb +15 -5
- data/lib/graphql/subscriptions.rb +108 -35
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
- data/lib/graphql/tracing/appoptics_tracing.rb +171 -0
- data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
- data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
- data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
- data/lib/graphql/tracing/platform_tracing.rb +53 -9
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
- data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
- data/lib/graphql/tracing/scout_tracing.rb +19 -0
- data/lib/graphql/tracing/skylight_tracing.rb +8 -0
- data/lib/graphql/tracing/statsd_tracing.rb +42 -0
- data/lib/graphql/tracing.rb +14 -34
- data/lib/graphql/types/big_int.rb +1 -1
- data/lib/graphql/types/int.rb +9 -2
- data/lib/graphql/types/iso_8601_date.rb +3 -3
- data/lib/graphql/types/iso_8601_date_time.rb +25 -10
- data/lib/graphql/types/relay/base_connection.rb +11 -7
- data/lib/graphql/types/relay/base_edge.rb +2 -1
- data/lib/graphql/types/string.rb +7 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/union_type.rb +13 -28
- data/lib/graphql/unresolved_type_error.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +31 -6
- data/readme.md +1 -1
- metadata +34 -9
- data/lib/graphql/literal_validation_error.rb +0 -6
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "graphql/execution/interpreter/argument_value"
|
3
|
+
require "graphql/execution/interpreter/arguments"
|
4
|
+
require "graphql/execution/interpreter/arguments_cache"
|
2
5
|
require "graphql/execution/interpreter/execution_errors"
|
3
6
|
require "graphql/execution/interpreter/hash_response"
|
4
7
|
require "graphql/execution/interpreter/runtime"
|
5
8
|
require "graphql/execution/interpreter/resolve"
|
9
|
+
require "graphql/execution/interpreter/handles_raw_value"
|
6
10
|
|
7
11
|
module GraphQL
|
8
12
|
module Execution
|
@@ -17,17 +21,13 @@ module GraphQL
|
|
17
21
|
runtime.final_value
|
18
22
|
end
|
19
23
|
|
20
|
-
def self.use(
|
21
|
-
|
22
|
-
|
23
|
-
schema_class
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
schema_config.query_execution_strategy(GraphQL::Execution::Interpreter)
|
28
|
-
schema_config.mutation_execution_strategy(GraphQL::Execution::Interpreter)
|
29
|
-
schema_config.subscription_execution_strategy(GraphQL::Execution::Interpreter)
|
30
|
-
end
|
24
|
+
def self.use(schema_class)
|
25
|
+
schema_class.interpreter = true
|
26
|
+
schema_class.query_execution_strategy(GraphQL::Execution::Interpreter)
|
27
|
+
schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter)
|
28
|
+
schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter)
|
29
|
+
schema_class.add_subscription_extension_if_necessary
|
30
|
+
GraphQL::Schema::Object.include(HandlesRawValue)
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.begin_multiplex(multiplex)
|
@@ -93,6 +93,29 @@ module GraphQL
|
|
93
93
|
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
94
94
|
Interpreter::Resolve.resolve_all(final_values)
|
95
95
|
end
|
96
|
+
queries.each do |query|
|
97
|
+
runtime = query.context.namespace(:interpreter)[:runtime]
|
98
|
+
if runtime
|
99
|
+
runtime.delete_interpreter_context(:current_path)
|
100
|
+
runtime.delete_interpreter_context(:current_field)
|
101
|
+
runtime.delete_interpreter_context(:current_object)
|
102
|
+
runtime.delete_interpreter_context(:current_arguments)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
class ListResultFailedError < GraphQL::Error
|
109
|
+
def initialize(value:, path:, field:)
|
110
|
+
message = "Failed to build a GraphQL list result for field `#{field.path}` at path `#{path.join(".")}`.\n".dup
|
111
|
+
|
112
|
+
message << "Expected `#{value.inspect}` to implement `.each` to satisfy the GraphQL return type `#{field.type.to_type_signature}`.\n"
|
113
|
+
|
114
|
+
if field.connection?
|
115
|
+
message << "\nThis field was treated as a Relay-style connection; add `connection: false` to the `field(...)` to disable this behavior."
|
116
|
+
end
|
117
|
+
super(message)
|
118
|
+
end
|
96
119
|
end
|
97
120
|
end
|
98
121
|
end
|
@@ -51,7 +51,17 @@ module GraphQL
|
|
51
51
|
|
52
52
|
# @return [Hash<Symbol, Object>]
|
53
53
|
def arguments
|
54
|
-
|
54
|
+
if defined?(@arguments)
|
55
|
+
@arguments
|
56
|
+
else
|
57
|
+
@arguments = if @field
|
58
|
+
@query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
|
59
|
+
args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
|
60
|
+
end
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
55
65
|
end
|
56
66
|
|
57
67
|
# True if this node has a selection on `field_name`.
|
@@ -81,7 +91,7 @@ module GraphQL
|
|
81
91
|
def selection(field_name, selected_type: @selected_type, arguments: nil)
|
82
92
|
next_field_name = normalize_name(field_name)
|
83
93
|
|
84
|
-
next_field_defn =
|
94
|
+
next_field_defn = get_class_based_field(selected_type, next_field_name)
|
85
95
|
if next_field_defn
|
86
96
|
next_nodes = []
|
87
97
|
@ast_nodes.each do |ast_node|
|
@@ -127,7 +137,7 @@ module GraphQL
|
|
127
137
|
|
128
138
|
subselections_by_type.each do |type, ast_nodes_by_response_key|
|
129
139
|
ast_nodes_by_response_key.each do |response_key, ast_nodes|
|
130
|
-
field_defn =
|
140
|
+
field_defn = get_class_based_field(type, ast_nodes.first.name)
|
131
141
|
lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type)
|
132
142
|
subselections.push(lookahead)
|
133
143
|
end
|
@@ -203,8 +213,29 @@ module GraphQL
|
|
203
213
|
end
|
204
214
|
end
|
205
215
|
|
216
|
+
# Wrap get_field and ensure that it returns a GraphQL::Schema::Field.
|
217
|
+
# Remove this when legacy execution is removed.
|
218
|
+
def get_class_based_field(type, name)
|
219
|
+
f = @query.get_field(type, name)
|
220
|
+
f && f.type_class
|
221
|
+
end
|
222
|
+
|
223
|
+
def skipped_by_directive?(ast_selection)
|
224
|
+
ast_selection.directives.each do |directive|
|
225
|
+
dir_defn = @query.schema.directives.fetch(directive.name)
|
226
|
+
directive_class = dir_defn.type_class
|
227
|
+
if directive_class
|
228
|
+
dir_args = @query.arguments_for(directive, dir_defn)
|
229
|
+
return true unless directive_class.static_include?(dir_args, @query.context)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
false
|
233
|
+
end
|
234
|
+
|
206
235
|
def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments)
|
207
236
|
ast_selections.each do |ast_selection|
|
237
|
+
next if skipped_by_directive?(ast_selection)
|
238
|
+
|
208
239
|
case ast_selection
|
209
240
|
when GraphQL::Language::Nodes::Field
|
210
241
|
response_key = ast_selection.alias || ast_selection.name
|
@@ -213,7 +244,7 @@ module GraphQL
|
|
213
244
|
elsif arguments.nil? || arguments.empty?
|
214
245
|
selections_on_type[response_key] = [ast_selection]
|
215
246
|
else
|
216
|
-
field_defn =
|
247
|
+
field_defn = get_class_based_field(selected_type, ast_selection.name)
|
217
248
|
if arguments_match?(arguments, field_defn, ast_selection)
|
218
249
|
selections_on_type[response_key] = [ast_selection]
|
219
250
|
end
|
@@ -223,14 +254,14 @@ module GraphQL
|
|
223
254
|
subselections_on_type = selections_on_type
|
224
255
|
if (t = ast_selection.type)
|
225
256
|
# Assuming this is valid, that `t` will be found.
|
226
|
-
on_type = @query.schema.
|
257
|
+
on_type = @query.schema.get_type(t.name).type_class
|
227
258
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
228
259
|
end
|
229
260
|
find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments)
|
230
261
|
when GraphQL::Language::Nodes::FragmentSpread
|
231
262
|
frag_defn = @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})")
|
232
263
|
# Again, assuming a valid AST
|
233
|
-
on_type = @query.schema.
|
264
|
+
on_type = @query.schema.get_type(frag_defn.type.name).type_class
|
234
265
|
subselections_on_type = subselections_by_type[on_type] ||= {}
|
235
266
|
find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments)
|
236
267
|
else
|
@@ -242,6 +273,7 @@ module GraphQL
|
|
242
273
|
# If a selection on `node` matches `field_name` (which is backed by `field_defn`)
|
243
274
|
# and matches the `arguments:` constraints, then add that node to `matches`
|
244
275
|
def find_selected_nodes(node, field_name, field_defn, arguments:, matches:)
|
276
|
+
return if skipped_by_directive?(node)
|
245
277
|
case node
|
246
278
|
when GraphQL::Language::Nodes::Field
|
247
279
|
if node.name == field_name
|
@@ -263,120 +295,13 @@ module GraphQL
|
|
263
295
|
end
|
264
296
|
|
265
297
|
def arguments_match?(arguments, field_defn, field_node)
|
266
|
-
query_kwargs =
|
298
|
+
query_kwargs = @query.arguments_for(field_node, field_defn)
|
267
299
|
arguments.all? do |arg_name, arg_value|
|
268
300
|
arg_name = normalize_keyword(arg_name)
|
269
301
|
# Make sure the constraint is present with a matching value
|
270
302
|
query_kwargs.key?(arg_name) && query_kwargs[arg_name] == arg_value
|
271
303
|
end
|
272
304
|
end
|
273
|
-
|
274
|
-
# TODO Dedup with interpreter
|
275
|
-
module ArgumentHelpers
|
276
|
-
module_function
|
277
|
-
|
278
|
-
def arguments(query, graphql_object, arg_owner, ast_node)
|
279
|
-
kwarg_arguments = {}
|
280
|
-
arg_defns = arg_owner.arguments
|
281
|
-
ast_node.arguments.each do |arg|
|
282
|
-
arg_defn = arg_defns[arg.name] || raise("Invariant: missing argument definition for #{arg.name.inspect} in #{arg_defns.keys} from #{arg_owner}")
|
283
|
-
# Need to distinguish between client-provided `nil`
|
284
|
-
# and nothing-at-all
|
285
|
-
is_present, value = arg_to_value(query, graphql_object, arg_defn.type, arg.value)
|
286
|
-
if is_present
|
287
|
-
# This doesn't apply to directives, which are legacy
|
288
|
-
# Can remove this when Skip and Include use classes or something.
|
289
|
-
if graphql_object
|
290
|
-
value = arg_defn.prepare_value(graphql_object, value)
|
291
|
-
end
|
292
|
-
kwarg_arguments[arg_defn.keyword] = value
|
293
|
-
end
|
294
|
-
end
|
295
|
-
arg_defns.each do |name, arg_defn|
|
296
|
-
if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
|
297
|
-
kwarg_arguments[arg_defn.keyword] = arg_defn.default_value
|
298
|
-
end
|
299
|
-
end
|
300
|
-
kwarg_arguments
|
301
|
-
end
|
302
|
-
|
303
|
-
# Get a Ruby-ready value from a client query.
|
304
|
-
# @param graphql_object [Object] The owner of the field whose argument this is
|
305
|
-
# @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
|
306
|
-
# @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
|
307
|
-
# @return [Array(is_present, value)]
|
308
|
-
def arg_to_value(query, graphql_object, arg_type, ast_value)
|
309
|
-
if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
310
|
-
# If it's not here, it will get added later
|
311
|
-
if query.variables.key?(ast_value.name)
|
312
|
-
return true, query.variables[ast_value.name]
|
313
|
-
else
|
314
|
-
return false, nil
|
315
|
-
end
|
316
|
-
elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
|
317
|
-
return true, nil
|
318
|
-
elsif arg_type.is_a?(GraphQL::Schema::NonNull)
|
319
|
-
arg_to_value(query, graphql_object, arg_type.of_type, ast_value)
|
320
|
-
elsif arg_type.is_a?(GraphQL::Schema::List)
|
321
|
-
# Treat a single value like a list
|
322
|
-
arg_value = Array(ast_value)
|
323
|
-
list = []
|
324
|
-
arg_value.map do |inner_v|
|
325
|
-
_present, value = arg_to_value(query, graphql_object, arg_type.of_type, inner_v)
|
326
|
-
list << value
|
327
|
-
end
|
328
|
-
return true, list
|
329
|
-
elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
|
330
|
-
# For these, `prepare` is applied during `#initialize`.
|
331
|
-
# Pass `nil` so it will be skipped in `#arguments`.
|
332
|
-
# What a mess.
|
333
|
-
args = arguments(query, nil, arg_type, ast_value)
|
334
|
-
# We're not tracking defaults_used, but for our purposes
|
335
|
-
# we compare the value to the default value.
|
336
|
-
return true, arg_type.new(ruby_kwargs: args, context: query.context, defaults_used: nil)
|
337
|
-
else
|
338
|
-
flat_value = flatten_ast_value(query, ast_value)
|
339
|
-
return true, arg_type.coerce_input(flat_value, query.context)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
def flatten_ast_value(query, v)
|
344
|
-
case v
|
345
|
-
when GraphQL::Language::Nodes::Enum
|
346
|
-
v.name
|
347
|
-
when GraphQL::Language::Nodes::InputObject
|
348
|
-
h = {}
|
349
|
-
v.arguments.each do |arg|
|
350
|
-
h[arg.name] = flatten_ast_value(query, arg.value)
|
351
|
-
end
|
352
|
-
h
|
353
|
-
when Array
|
354
|
-
v.map { |v2| flatten_ast_value(query, v2) }
|
355
|
-
when GraphQL::Language::Nodes::VariableIdentifier
|
356
|
-
flatten_ast_value(query.variables[v.name])
|
357
|
-
else
|
358
|
-
v
|
359
|
-
end
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
|
-
# TODO dedup with interpreter
|
364
|
-
module FieldHelpers
|
365
|
-
module_function
|
366
|
-
|
367
|
-
def get_field(schema, owner_type, field_name)
|
368
|
-
field_defn = owner_type.get_field(field_name)
|
369
|
-
field_defn ||= if owner_type == schema.query.metadata[:type_class] && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
370
|
-
entry_point_field.metadata[:type_class]
|
371
|
-
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
|
372
|
-
dynamic_field.metadata[:type_class]
|
373
|
-
else
|
374
|
-
nil
|
375
|
-
end
|
376
|
-
|
377
|
-
field_defn
|
378
|
-
end
|
379
|
-
end
|
380
305
|
end
|
381
306
|
end
|
382
307
|
end
|
@@ -34,8 +34,7 @@ module GraphQL
|
|
34
34
|
@schema = schema
|
35
35
|
@queries = queries
|
36
36
|
@context = context
|
37
|
-
|
38
|
-
@tracers = schema.tracers + GraphQL::Tracing.tracers + (context[:tracers] || [])
|
37
|
+
@tracers = schema.tracers + (context[:tracers] || [])
|
39
38
|
# Support `context: {backtrace: true}`
|
40
39
|
if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
|
41
40
|
@tracers << GraphQL::Backtrace::Tracer
|
@@ -44,9 +43,9 @@ module GraphQL
|
|
44
43
|
end
|
45
44
|
|
46
45
|
class << self
|
47
|
-
def run_all(schema, query_options,
|
48
|
-
queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, opts) }
|
49
|
-
run_queries(schema, queries,
|
46
|
+
def run_all(schema, query_options, **kwargs)
|
47
|
+
queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, **opts) }
|
48
|
+
run_queries(schema, queries, **kwargs)
|
50
49
|
end
|
51
50
|
|
52
51
|
# @param schema [GraphQL::Schema]
|
@@ -95,6 +94,7 @@ module GraphQL
|
|
95
94
|
query.result
|
96
95
|
end
|
97
96
|
rescue Exception
|
97
|
+
# TODO rescue at a higher level so it will catch errors in analysis, too
|
98
98
|
# Assign values here so that the query's `@executed` becomes true
|
99
99
|
queries.map { |q| q.result_values ||= {} }
|
100
100
|
raise
|
@@ -173,6 +173,15 @@ module GraphQL
|
|
173
173
|
def instrument_and_analyze(multiplex)
|
174
174
|
GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do
|
175
175
|
schema = multiplex.schema
|
176
|
+
if schema.interpreter? && schema.analysis_engine != GraphQL::Analysis::AST
|
177
|
+
raise <<-ERR
|
178
|
+
Can't use `GraphQL::Execution::Interpreter` without `GraphQL::Analysis::AST`, please add this plugin to your schema:
|
179
|
+
|
180
|
+
use GraphQL::Analysis::AST
|
181
|
+
|
182
|
+
For information about the new analysis engine: https://graphql-ruby.org/queries/ast_analysis.html
|
183
|
+
ERR
|
184
|
+
end
|
176
185
|
multiplex_analyzers = schema.multiplex_analyzers
|
177
186
|
if multiplex.max_complexity
|
178
187
|
multiplex_analyzers += if schema.using_ast_analysis?
|
data/lib/graphql/field.rb
CHANGED
@@ -2,123 +2,7 @@
|
|
2
2
|
require "graphql/field/resolve"
|
3
3
|
|
4
4
|
module GraphQL
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# They're usually created with the `field` helper. If you create it by hand, make sure {#name} is a String.
|
8
|
-
#
|
9
|
-
# A field must have a return type, but if you want to defer the return type calculation until later,
|
10
|
-
# you can pass a proc for the return type. That proc will be called when the schema is defined.
|
11
|
-
#
|
12
|
-
# @example Lazy type resolution
|
13
|
-
# # If the field's type isn't defined yet, you can pass a proc
|
14
|
-
# field :city, -> { TypeForModelName.find("City") }
|
15
|
-
#
|
16
|
-
# For complex field definition, you can pass a block to the `field` helper, eg `field :name do ... end`.
|
17
|
-
# This block is equivalent to calling `GraphQL::Field.define { ... }`.
|
18
|
-
#
|
19
|
-
# @example Defining a field with a block
|
20
|
-
# field :city, CityType do
|
21
|
-
# # field definition continues inside the block
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# ## Resolve
|
25
|
-
#
|
26
|
-
# Fields have `resolve` functions to determine their values at query-time.
|
27
|
-
# The default implementation is to call a method on the object based on the field name.
|
28
|
-
#
|
29
|
-
# @example Create a field which calls a method with the same name.
|
30
|
-
# GraphQL::ObjectType.define do
|
31
|
-
# field :name, types.String, "The name of this thing "
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# You can specify a custom proc with the `resolve` helper.
|
35
|
-
#
|
36
|
-
# There are some shortcuts for common `resolve` implementations:
|
37
|
-
# - Provide `property:` to call a method with a different name than the field name
|
38
|
-
# - Provide `hash_key:` to resolve the field by doing a key lookup, eg `obj[:my_hash_key]`
|
39
|
-
#
|
40
|
-
# @example Create a field that calls a different method on the object
|
41
|
-
# GraphQL::ObjectType.define do
|
42
|
-
# # use the `property` keyword:
|
43
|
-
# field :firstName, types.String, property: :first_name
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# @example Create a field looks up with `[hash_key]`
|
47
|
-
# GraphQL::ObjectType.define do
|
48
|
-
# # use the `hash_key` keyword:
|
49
|
-
# field :firstName, types.String, hash_key: :first_name
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# ## Arguments
|
53
|
-
#
|
54
|
-
# Fields can take inputs; they're called arguments. You can define them with the `argument` helper.
|
55
|
-
#
|
56
|
-
# @example Create a field with an argument
|
57
|
-
# field :students, types[StudentType] do
|
58
|
-
# argument :grade, types.Int
|
59
|
-
# resolve ->(obj, args, ctx) {
|
60
|
-
# Student.where(grade: args[:grade])
|
61
|
-
# }
|
62
|
-
# end
|
63
|
-
#
|
64
|
-
# They can have default values which will be provided to `resolve` if the query doesn't include a value.
|
65
|
-
#
|
66
|
-
# @example Argument with a default value
|
67
|
-
# field :events, types[EventType] do
|
68
|
-
# # by default, don't include past events
|
69
|
-
# argument :includePast, types.Boolean, default_value: false
|
70
|
-
# resolve ->(obj, args, ctx) {
|
71
|
-
# args[:includePast] # => false if no value was provided in the query
|
72
|
-
# # ...
|
73
|
-
# }
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# Only certain types maybe used for inputs:
|
77
|
-
#
|
78
|
-
# - Scalars
|
79
|
-
# - Enums
|
80
|
-
# - Input Objects
|
81
|
-
# - Lists of those types
|
82
|
-
#
|
83
|
-
# Input types may also be non-null -- in that case, the query will fail
|
84
|
-
# if the input is not present.
|
85
|
-
#
|
86
|
-
# ## Complexity
|
87
|
-
#
|
88
|
-
# Fields can have _complexity_ values which describe the computation cost of resolving the field.
|
89
|
-
# You can provide the complexity as a constant with `complexity:` or as a proc, with the `complexity` helper.
|
90
|
-
#
|
91
|
-
# @example Custom complexity values
|
92
|
-
# # Complexity can be a number or a proc.
|
93
|
-
#
|
94
|
-
# # Complexity can be defined with a keyword:
|
95
|
-
# field :expensive_calculation, !types.Int, complexity: 10
|
96
|
-
#
|
97
|
-
# # Or inside the block:
|
98
|
-
# field :expensive_calculation_2, !types.Int do
|
99
|
-
# complexity ->(ctx, args, child_complexity) { ctx[:current_user].staff? ? 0 : 10 }
|
100
|
-
# end
|
101
|
-
#
|
102
|
-
# @example Calculating the complexity of a list field
|
103
|
-
# field :items, types[ItemType] do
|
104
|
-
# argument :limit, !types.Int
|
105
|
-
# # Multiply the child complexity by the possible items on the list
|
106
|
-
# complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }
|
107
|
-
# end
|
108
|
-
#
|
109
|
-
# @example Creating a field, then assigning it to a type
|
110
|
-
# name_field = GraphQL::Field.define do
|
111
|
-
# name("Name")
|
112
|
-
# type(!types.String)
|
113
|
-
# description("The name of this thing")
|
114
|
-
# resolve ->(object, arguments, context) { object.name }
|
115
|
-
# end
|
116
|
-
#
|
117
|
-
# NamedType = GraphQL::ObjectType.define do
|
118
|
-
# # The second argument may be a GraphQL::Field
|
119
|
-
# field :name, name_field
|
120
|
-
# end
|
121
|
-
#
|
5
|
+
# @api deprecated
|
122
6
|
class Field
|
123
7
|
include GraphQL::Define::InstanceDefinable
|
124
8
|
accepts_definitions :name, :description, :deprecation_reason,
|
@@ -196,6 +80,10 @@ module GraphQL
|
|
196
80
|
|
197
81
|
attr_accessor :ast_node
|
198
82
|
|
83
|
+
# Future-compatible alias
|
84
|
+
# @see {GraphQL::SchemaMember}
|
85
|
+
alias :graphql_definition :itself
|
86
|
+
|
199
87
|
# @return [Boolean]
|
200
88
|
def connection?
|
201
89
|
@connection
|
@@ -266,7 +154,7 @@ module GraphQL
|
|
266
154
|
end
|
267
155
|
|
268
156
|
def name=(new_name)
|
269
|
-
old_name = @name
|
157
|
+
old_name = defined?(@name) ? @name : nil
|
270
158
|
@name = new_name
|
271
159
|
|
272
160
|
if old_name != new_name && @resolve_proc.is_a?(Field::Resolve::NameResolve)
|
@@ -315,6 +203,14 @@ module GraphQL
|
|
315
203
|
}
|
316
204
|
end
|
317
205
|
|
206
|
+
def type_class
|
207
|
+
metadata[:type_class]
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_argument(argument_name)
|
211
|
+
arguments[argument_name]
|
212
|
+
end
|
213
|
+
|
318
214
|
private
|
319
215
|
|
320
216
|
def build_default_resolver
|
data/lib/graphql/filter.rb
CHANGED
data/lib/graphql/function.rb
CHANGED
@@ -1,35 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Class-level values defined with the DSL will be inherited,
|
6
|
-
# so {GraphQL::Function}s can extend one another.
|
7
|
-
#
|
8
|
-
# It's OK to override the instance methods here in order to customize behavior of instances.
|
9
|
-
#
|
10
|
-
# @example A reusable GraphQL::Function attached as a field
|
11
|
-
# class FindRecord < GraphQL::Function
|
12
|
-
# attr_reader :type
|
13
|
-
#
|
14
|
-
# def initialize(model:, type:)
|
15
|
-
# @model = model
|
16
|
-
# @type = type
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# argument :id, GraphQL::ID_TYPE
|
20
|
-
#
|
21
|
-
# def call(obj, args, ctx)
|
22
|
-
# @model.find(args.id)
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# QueryType = GraphQL::ObjectType.define do
|
27
|
-
# name "Query"
|
28
|
-
# field :post, function: FindRecord.new(model: Post, type: PostType)
|
29
|
-
# field :comment, function: FindRecord.new(model: Comment, type: CommentType)
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# @see {GraphQL::Schema::Resolver} for a replacement for `GraphQL::Function`
|
3
|
+
# @api deprecated
|
33
4
|
class Function
|
34
5
|
# @return [Hash<String => GraphQL::Argument>] Arguments, keyed by name
|
35
6
|
def arguments
|
@@ -1,28 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Input objects have _arguments_ which are identical to {GraphQL::Field} arguments.
|
6
|
-
# They map names to types and support default values.
|
7
|
-
# Their input types can be any input types, including {InputObjectType}s.
|
8
|
-
#
|
9
|
-
# @example An input type with name and number
|
10
|
-
# PlayerInput = GraphQL::InputObjectType.define do
|
11
|
-
# name("Player")
|
12
|
-
# argument :name, !types.String
|
13
|
-
# argument :number, !types.Int
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# In a `resolve` function, you can access the values by making nested lookups on `args`.
|
17
|
-
#
|
18
|
-
# @example Accessing input values in a resolve function
|
19
|
-
# resolve ->(obj, args, ctx) {
|
20
|
-
# args[:player][:name] # => "Tony Gwynn"
|
21
|
-
# args[:player][:number] # => 19
|
22
|
-
# args[:player].to_h # { "name" => "Tony Gwynn", "number" => 19 }
|
23
|
-
# # ...
|
24
|
-
# }
|
25
|
-
#
|
3
|
+
# @api deprecated
|
26
4
|
class InputObjectType < GraphQL::BaseType
|
27
5
|
accepts_definitions(
|
28
6
|
:arguments, :mutation,
|
@@ -80,6 +58,10 @@ module GraphQL
|
|
80
58
|
result
|
81
59
|
end
|
82
60
|
|
61
|
+
def get_argument(argument_name)
|
62
|
+
arguments[argument_name]
|
63
|
+
end
|
64
|
+
|
83
65
|
private
|
84
66
|
|
85
67
|
def coerce_non_null_input(value, ctx)
|
@@ -136,7 +118,7 @@ module GraphQL
|
|
136
118
|
# Items in the input that are unexpected
|
137
119
|
input.each do |name, value|
|
138
120
|
if visible_arguments_map[name].nil?
|
139
|
-
result.add_problem("Field is not defined on #{self.
|
121
|
+
result.add_problem("Field is not defined on #{self.graphql_name}", [name])
|
140
122
|
end
|
141
123
|
end
|
142
124
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
# This error is raised when `Types::Int` is given an input value outside of 32-bit integer range.
|
4
|
+
#
|
5
|
+
# For really big integer values, consider `GraphQL::Types::BigInt`
|
6
|
+
#
|
7
|
+
# @see GraphQL::Types::Int which raises this error
|
8
|
+
class IntegerDecodingError < GraphQL::RuntimeTypeError
|
9
|
+
# The value which couldn't be decoded
|
10
|
+
attr_reader :integer_value
|
11
|
+
|
12
|
+
def initialize(value)
|
13
|
+
@integer_value = value
|
14
|
+
super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,31 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GraphQL
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Interfaces can have fields, defined with `field`, just like an object type.
|
6
|
-
#
|
7
|
-
# Objects which implement this field _inherit_ field definitions from the interface.
|
8
|
-
# An object type can override the inherited definition by redefining that field.
|
9
|
-
#
|
10
|
-
# @example An interface with three fields
|
11
|
-
# DeviceInterface = GraphQL::InterfaceType.define do
|
12
|
-
# name("Device")
|
13
|
-
# description("Hardware devices for computing")
|
14
|
-
#
|
15
|
-
# field :ram, types.String
|
16
|
-
# field :processor, ProcessorType
|
17
|
-
# field :release_year, types.Int
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# @example Implementing an interface with an object type
|
21
|
-
# Laptoptype = GraphQL::ObjectType.define do
|
22
|
-
# interfaces [DeviceInterface]
|
23
|
-
# end
|
24
|
-
#
|
3
|
+
# @api deprecated
|
25
4
|
class InterfaceType < GraphQL::BaseType
|
26
5
|
accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField
|
27
6
|
|
28
7
|
attr_accessor :fields, :orphan_types, :resolve_type_proc
|
8
|
+
attr_writer :type_membership_class
|
29
9
|
ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type
|
30
10
|
|
31
11
|
def initialize
|
@@ -71,7 +51,7 @@ module GraphQL
|
|
71
51
|
# @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and implements this {InterfaceType}, (else `nil`)
|
72
52
|
def get_possible_type(type_name, ctx)
|
73
53
|
type = ctx.query.get_type(type_name)
|
74
|
-
type if type && ctx.query.
|
54
|
+
type if type && ctx.query.warden.possible_types(self).include?(type)
|
75
55
|
end
|
76
56
|
|
77
57
|
# Check if a type is a possible type of this {InterfaceType}
|
@@ -82,5 +62,9 @@ module GraphQL
|
|
82
62
|
type_name = type.is_a?(String) ? type : type.graphql_name
|
83
63
|
!get_possible_type(type_name, ctx).nil?
|
84
64
|
end
|
65
|
+
|
66
|
+
def type_membership_class
|
67
|
+
@type_membership_class || GraphQL::Schema::TypeMembership
|
68
|
+
end
|
85
69
|
end
|
86
70
|
end
|
@@ -66,11 +66,11 @@ module GraphQL
|
|
66
66
|
# Call the block for each type in `self`.
|
67
67
|
# This uses the simplest possible expression of `self`,
|
68
68
|
# so if this scope is defined by an abstract type, it gets yielded.
|
69
|
-
def each
|
69
|
+
def each(&block)
|
70
70
|
if @abstract_type
|
71
71
|
yield(@type)
|
72
72
|
else
|
73
|
-
@types.each
|
73
|
+
@types.each(&block)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|