graphql 1.8.3 → 1.8.4
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.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -1
- data/lib/graphql/argument.rb +1 -0
- data/lib/graphql/authorization.rb +81 -0
- data/lib/graphql/boolean_type.rb +0 -1
- data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +2 -1
- data/lib/graphql/execution/execute.rb +34 -10
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/field.rb +7 -1
- data/lib/graphql/float_type.rb +0 -1
- data/lib/graphql/id_type.rb +0 -1
- data/lib/graphql/int_type.rb +0 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/object_type.rb +3 -3
- data/lib/graphql/query.rb +6 -0
- data/lib/graphql/query/arguments.rb +2 -0
- data/lib/graphql/query/context.rb +6 -0
- data/lib/graphql/query/variables.rb +7 -1
- data/lib/graphql/relay/connection_instrumentation.rb +2 -2
- data/lib/graphql/relay/connection_resolve.rb +7 -27
- data/lib/graphql/relay/connection_type.rb +1 -0
- data/lib/graphql/relay/edge_type.rb +1 -0
- data/lib/graphql/relay/edges_instrumentation.rb +9 -25
- data/lib/graphql/relay/mutation/instrumentation.rb +1 -2
- data/lib/graphql/relay/mutation/resolve.rb +2 -4
- data/lib/graphql/relay/node.rb +1 -6
- data/lib/graphql/relay/page_info.rb +1 -9
- data/lib/graphql/schema.rb +84 -11
- data/lib/graphql/schema/argument.rb +13 -0
- data/lib/graphql/schema/enum.rb +1 -1
- data/lib/graphql/schema/enum_value.rb +4 -0
- data/lib/graphql/schema/field.rb +44 -11
- data/lib/graphql/schema/interface.rb +20 -0
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +25 -3
- data/lib/graphql/schema/member/instrumentation.rb +15 -17
- data/lib/graphql/schema/mutation.rb +4 -0
- data/lib/graphql/schema/object.rb +33 -0
- data/lib/graphql/schema/possible_types.rb +2 -0
- data/lib/graphql/schema/resolver.rb +10 -0
- data/lib/graphql/schema/traversal.rb +9 -2
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +11 -2
- data/lib/graphql/string_type.rb +0 -1
- data/lib/graphql/types.rb +7 -0
- data/lib/graphql/types/relay.rb +31 -0
- data/lib/graphql/types/relay/base_connection.rb +87 -0
- data/lib/graphql/types/relay/base_edge.rb +51 -0
- data/lib/graphql/types/relay/base_field.rb +22 -0
- data/lib/graphql/types/relay/base_interface.rb +29 -0
- data/lib/graphql/types/relay/base_object.rb +26 -0
- data/lib/graphql/types/relay/node.rb +18 -0
- data/lib/graphql/types/relay/page_info.rb +23 -0
- data/lib/graphql/unauthorized_error.rb +20 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/authorization_spec.rb +684 -0
- data/spec/graphql/query/variables_spec.rb +20 -0
- data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
- data/spec/graphql/schema/resolver_spec.rb +31 -0
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +52 -0
- data/spec/support/dummy/schema.rb +16 -0
- data/spec/support/star_wars/schema.rb +28 -17
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 879e9096d14bb82f2c28f57c60d34e66c6706181
|
4
|
+
data.tar.gz: c39e6b2c81303fba7c2dfa1527932af7d8eb9d31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79d69e540d7cb3063ce6fea65286f0e66634edeced066930cbc63de169eb9f2a53412f606aacfbf02e28fccfd11781122e95cc31ee234073c6b0f3246c7ac26e
|
7
|
+
data.tar.gz: 4fe1822b9f021f064a2ba383c65a8c889a4acd08927f0c99ed9d9e473890474c3529afa97ed5cb95fd4f67f5d7754b595cdd654734a2fa595ecfebebeb4ff2a8
|
data/lib/graphql.rb
CHANGED
@@ -67,8 +67,9 @@ require "graphql/language"
|
|
67
67
|
require "graphql/analysis"
|
68
68
|
require "graphql/tracing"
|
69
69
|
require "graphql/execution"
|
70
|
-
require "graphql/relay"
|
71
70
|
require "graphql/schema"
|
71
|
+
require "graphql/types"
|
72
|
+
require "graphql/relay"
|
72
73
|
require "graphql/boolean_type"
|
73
74
|
require "graphql/float_type"
|
74
75
|
require "graphql/id_type"
|
@@ -98,3 +99,5 @@ require "graphql/parse_error"
|
|
98
99
|
require "graphql/backtrace"
|
99
100
|
|
100
101
|
require "graphql/deprecated_dsl"
|
102
|
+
require "graphql/authorization"
|
103
|
+
require "graphql/unauthorized_error"
|
data/lib/graphql/argument.rb
CHANGED
@@ -38,6 +38,7 @@ module GraphQL
|
|
38
38
|
accepts_definitions :name, :type, :description, :default_value, :as, :prepare
|
39
39
|
attr_accessor :type, :description, :default_value, :name, :as
|
40
40
|
attr_accessor :ast_node
|
41
|
+
alias :graphql_name :name
|
41
42
|
|
42
43
|
ensure_defined(:name, :description, :default_value, :type=, :type, :as, :expose_as, :prepare)
|
43
44
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
module Authorization
|
4
|
+
class InaccessibleFieldsError < GraphQL::AnalysisError
|
5
|
+
# @return [Array<Schema::Field, GraphQL::Field>] Fields that failed `.accessible?` checks
|
6
|
+
attr_reader :fields
|
7
|
+
|
8
|
+
# @return [GraphQL::Query::Context] The current query's context
|
9
|
+
attr_reader :context
|
10
|
+
|
11
|
+
# @return [Array<GraphQL::InternalRepresentation::Node>] The visited nodes that failed `.accessible?` checks
|
12
|
+
# @see {#fields} for the Field definitions
|
13
|
+
attr_reader :irep_nodes
|
14
|
+
|
15
|
+
def initialize(fields:, irep_nodes:, context:)
|
16
|
+
@fields = fields
|
17
|
+
@irep_nodes = irep_nodes
|
18
|
+
@context = context
|
19
|
+
super("Some fields in this query are not accessible: #{fields.map(&:graphql_name).join(", ")}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Analyzer
|
24
|
+
module_function
|
25
|
+
def initial_value(query)
|
26
|
+
{
|
27
|
+
schema: query.schema,
|
28
|
+
context: query.context,
|
29
|
+
inaccessible_nodes: [],
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(memo, visit_type, irep_node)
|
34
|
+
if visit_type == :enter
|
35
|
+
field = irep_node.definition
|
36
|
+
if field
|
37
|
+
schema = memo[:schema]
|
38
|
+
ctx = memo[:context]
|
39
|
+
next_field_accessible = schema.accessible?(field, ctx)
|
40
|
+
if !next_field_accessible
|
41
|
+
memo[:inaccessible_nodes] << irep_node
|
42
|
+
else
|
43
|
+
arg_accessible = true
|
44
|
+
irep_node.arguments.argument_values.each do |name, arg_value|
|
45
|
+
arg_accessible = schema.accessible?(arg_value.definition, ctx)
|
46
|
+
if !arg_accessible
|
47
|
+
memo[:inaccessible_nodes] << irep_node
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if arg_accessible
|
52
|
+
return_type = field.type.unwrap
|
53
|
+
next_type_accessible = schema.accessible?(return_type, ctx)
|
54
|
+
if !next_type_accessible
|
55
|
+
memo[:inaccessible_nodes] << irep_node
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
memo
|
62
|
+
end
|
63
|
+
|
64
|
+
def final_value(memo)
|
65
|
+
nodes = memo[:inaccessible_nodes]
|
66
|
+
if nodes.any?
|
67
|
+
fields = nodes.map do |node|
|
68
|
+
field_inst = node.definition
|
69
|
+
# Get the "source of truth" for this field
|
70
|
+
field_inst.metadata[:type_class] || field_inst
|
71
|
+
end
|
72
|
+
context = memo[:context]
|
73
|
+
err = InaccessibleFieldsError.new(fields: fields, irep_nodes: nodes, context: context)
|
74
|
+
context.schema.inaccessible_fields(err)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/graphql/boolean_type.rb
CHANGED
@@ -110,23 +110,47 @@ module GraphQL
|
|
110
110
|
err
|
111
111
|
end
|
112
112
|
|
113
|
-
|
114
|
-
# assign the lazy object to `.value=` so we can resolve it later.
|
115
|
-
# When we resolve it later, reassign it to `.value=` so that
|
116
|
-
# the finished value replaces the unfinished one.
|
117
|
-
#
|
118
|
-
# If the returned object is finished, continue to coerce
|
119
|
-
# and resolve child fields
|
120
|
-
if query.schema.lazy?(raw_value)
|
113
|
+
if field_ctx.schema.lazy?(raw_value)
|
121
114
|
field_ctx.value = Execution::Lazy.new {
|
122
115
|
inner_value = field_ctx.trace("execute_field_lazy", {context: field_ctx}) {
|
123
116
|
begin
|
124
|
-
|
117
|
+
begin
|
118
|
+
field_ctx.field.lazy_resolve(raw_value, arguments, field_ctx)
|
119
|
+
rescue GraphQL::UnauthorizedError => err
|
120
|
+
field_ctx.schema.unauthorized_object(err)
|
121
|
+
end
|
125
122
|
rescue GraphQL::ExecutionError => err
|
126
123
|
err
|
127
124
|
end
|
128
125
|
}
|
129
|
-
|
126
|
+
continue_or_wait(inner_value, arguments, field_ctx)
|
127
|
+
}
|
128
|
+
else
|
129
|
+
continue_or_wait(raw_value, arguments, field_ctx)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# If the returned object is lazy (unfinished),
|
134
|
+
# assign the lazy object to `.value=` so we can resolve it later.
|
135
|
+
# When we resolve it later, reassign it to `.value=` so that
|
136
|
+
# the finished value replaces the unfinished one.
|
137
|
+
#
|
138
|
+
# If the returned object is finished, continue to coerce
|
139
|
+
# and resolve child fields
|
140
|
+
def continue_or_wait(raw_value, arguments, field_ctx)
|
141
|
+
if (lazy_method = field_ctx.schema.lazy_method_name(raw_value))
|
142
|
+
field_ctx.value = Execution::Lazy.new {
|
143
|
+
inner_value = begin
|
144
|
+
begin
|
145
|
+
raw_value.public_send(lazy_method)
|
146
|
+
rescue GraphQL::UnauthorizedError => err
|
147
|
+
field_ctx.schema.unauthorized_object(err)
|
148
|
+
end
|
149
|
+
rescue GraphQL::ExecutionError => err
|
150
|
+
err
|
151
|
+
end
|
152
|
+
|
153
|
+
field_ctx.value = continue_or_wait(inner_value, arguments, field_ctx)
|
130
154
|
}
|
131
155
|
else
|
132
156
|
field_ctx.value = continue_resolve_field(raw_value, field_ctx)
|
data/lib/graphql/field.rb
CHANGED
@@ -157,6 +157,7 @@ module GraphQL
|
|
157
157
|
|
158
158
|
# @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType})
|
159
159
|
attr_accessor :name
|
160
|
+
alias :graphql_name :name
|
160
161
|
|
161
162
|
# @return [String, nil] The client-facing description of this field
|
162
163
|
attr_accessor :description
|
@@ -323,7 +324,12 @@ module GraphQL
|
|
323
324
|
module DefaultLazyResolve
|
324
325
|
def self.call(obj, args, ctx)
|
325
326
|
method_name = ctx.schema.lazy_method_name(obj)
|
326
|
-
obj.public_send(method_name)
|
327
|
+
next_obj = obj.public_send(method_name)
|
328
|
+
if ctx.schema.lazy?(next_obj)
|
329
|
+
call(next_obj, args, ctx)
|
330
|
+
else
|
331
|
+
next_obj
|
332
|
+
end
|
327
333
|
end
|
328
334
|
end
|
329
335
|
end
|
data/lib/graphql/float_type.rb
CHANGED
data/lib/graphql/id_type.rb
CHANGED
data/lib/graphql/int_type.rb
CHANGED
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
# Apply wrapping manually since this field isn't wrapped by instrumentation
|
12
12
|
schema = @context.query.schema
|
13
13
|
schema_type = schema.introspection_system.schema_type
|
14
|
-
schema_type.metadata[:type_class].
|
14
|
+
schema_type.metadata[:type_class].authorized_new(schema, @context)
|
15
15
|
end
|
16
16
|
|
17
17
|
def __type(name:)
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
19
19
|
if type
|
20
20
|
# Apply wrapping manually since this field isn't wrapped by instrumentation
|
21
21
|
type_type = @context.schema.introspection_system.type_type
|
22
|
-
type_type.metadata[:type_class].
|
22
|
+
type_type.metadata[:type_class].authorized_new(type, @context)
|
23
23
|
else
|
24
24
|
nil
|
25
25
|
end
|
data/lib/graphql/object_type.rb
CHANGED
@@ -22,11 +22,11 @@ module GraphQL
|
|
22
22
|
# end
|
23
23
|
#
|
24
24
|
class ObjectType < GraphQL::BaseType
|
25
|
-
accepts_definitions :interfaces, :fields, :mutation, field: GraphQL::Define::AssignObjectField
|
25
|
+
accepts_definitions :interfaces, :fields, :mutation, :relay_node_type, field: GraphQL::Define::AssignObjectField
|
26
26
|
accepts_definitions implements: ->(type, *interfaces, inherit: false) { type.implements(interfaces, inherit: inherit) }
|
27
27
|
|
28
|
-
attr_accessor :fields, :mutation
|
29
|
-
ensure_defined(:fields, :mutation, :interfaces)
|
28
|
+
attr_accessor :fields, :mutation, :relay_node_type
|
29
|
+
ensure_defined(:fields, :mutation, :interfaces, :relay_node_type)
|
30
30
|
|
31
31
|
# @!attribute fields
|
32
32
|
# @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
data/lib/graphql/query.rb
CHANGED
@@ -128,6 +128,12 @@ module GraphQL
|
|
128
128
|
|
129
129
|
@result_values = nil
|
130
130
|
@executed = false
|
131
|
+
|
132
|
+
# TODO add a general way to define schema-level filters
|
133
|
+
# TODO also add this to schema dumps
|
134
|
+
if @schema.respond_to?(:visible?)
|
135
|
+
merge_filters(only: @schema.method(:visible?))
|
136
|
+
end
|
131
137
|
end
|
132
138
|
|
133
139
|
def subscription_update?
|
@@ -207,8 +207,14 @@ module GraphQL
|
|
207
207
|
@query = context.query
|
208
208
|
@schema = context.schema
|
209
209
|
@tracers = @query.tracers
|
210
|
+
# This hack flag is required by ConnectionResolve
|
211
|
+
@wrapped_connection = false
|
212
|
+
@wrapped_object = false
|
210
213
|
end
|
211
214
|
|
215
|
+
# @api private
|
216
|
+
attr_accessor :wrapped_connection, :wrapped_object
|
217
|
+
|
212
218
|
def path
|
213
219
|
@path ||= @parent.path.dup << @key
|
214
220
|
end
|
@@ -30,7 +30,13 @@ module GraphQL
|
|
30
30
|
provided_value = @provided_variables[variable_name]
|
31
31
|
value_was_provided = @provided_variables.key?(variable_name)
|
32
32
|
|
33
|
-
|
33
|
+
begin
|
34
|
+
validation_result = variable_type.validate_input(provided_value, ctx)
|
35
|
+
rescue GraphQL::CoercionError => ex
|
36
|
+
validation_result = GraphQL::Query::InputValidationResult.new
|
37
|
+
validation_result.add_problem(ex.message)
|
38
|
+
end
|
39
|
+
|
34
40
|
if !validation_result.valid?
|
35
41
|
# This finds variables that were required but not provided
|
36
42
|
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
|
@@ -36,8 +36,8 @@ module GraphQL
|
|
36
36
|
connection_arguments = default_arguments.merge(field.arguments)
|
37
37
|
original_resolve = field.resolve_proc
|
38
38
|
original_lazy_resolve = field.lazy_resolve_proc
|
39
|
-
connection_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_resolve
|
40
|
-
connection_lazy_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_lazy_resolve
|
39
|
+
connection_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_resolve)
|
40
|
+
connection_lazy_resolve = GraphQL::Relay::ConnectionResolve.new(field, original_lazy_resolve)
|
41
41
|
field.redefine(
|
42
42
|
resolve: connection_resolve,
|
43
43
|
lazy_resolve: connection_lazy_resolve,
|
@@ -2,34 +2,24 @@
|
|
2
2
|
module GraphQL
|
3
3
|
module Relay
|
4
4
|
class ConnectionResolve
|
5
|
-
def initialize(field, underlying_resolve
|
5
|
+
def initialize(field, underlying_resolve)
|
6
6
|
@field = field
|
7
7
|
@underlying_resolve = underlying_resolve
|
8
8
|
@max_page_size = field.connection_max_page_size
|
9
|
-
@lazy = lazy
|
10
9
|
end
|
11
10
|
|
12
11
|
def call(obj, args, ctx)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
parent = obj
|
18
|
-
end
|
12
|
+
# in a lazy ressolve hook, obj is the promise,
|
13
|
+
# get the object that the promise was
|
14
|
+
# originally derived from
|
15
|
+
parent = ctx.object
|
19
16
|
|
20
17
|
nodes = @underlying_resolve.call(obj, args, ctx)
|
21
18
|
|
22
|
-
if nodes.nil?
|
23
|
-
nil
|
24
|
-
elsif ctx.schema.lazy?(nodes)
|
25
|
-
if !@lazy
|
26
|
-
LazyNodesWrapper.new(obj, nodes)
|
27
|
-
else
|
28
|
-
nodes
|
29
|
-
end
|
30
|
-
elsif nodes.is_a?(GraphQL::Execution::Execute::Skip)
|
19
|
+
if nodes.nil? || ctx.schema.lazy?(nodes) || nodes.is_a?(GraphQL::Execution::Execute::Skip) || ctx.wrapped_connection
|
31
20
|
nodes
|
32
21
|
else
|
22
|
+
ctx.wrapped_connection = true
|
33
23
|
build_connection(nodes, args, parent, ctx)
|
34
24
|
end
|
35
25
|
end
|
@@ -48,16 +38,6 @@ module GraphQL
|
|
48
38
|
connection_class.new(nodes, args, field: @field, max_page_size: @max_page_size, parent: parent, context: ctx)
|
49
39
|
end
|
50
40
|
end
|
51
|
-
|
52
|
-
# A container for the proper `parent` of connection nodes.
|
53
|
-
# Without this wrapper, the lazy object _itself_ is passed into `build_connection`
|
54
|
-
# and it becomes the parent, which is wrong.
|
55
|
-
#
|
56
|
-
# We can get away with it because we know that this instrumentation will be applied last.
|
57
|
-
# That means its code after `underlying_resolve` will be _last_ on the way in.
|
58
|
-
# And, its code before `underlying_resolve` will be _first_ during lazy resolution.
|
59
|
-
# @api private
|
60
|
-
LazyNodesWrapper = Struct.new(:parent, :lazy_object)
|
61
41
|
end
|
62
42
|
end
|
63
43
|
end
|
@@ -9,6 +9,7 @@ module GraphQL
|
|
9
9
|
description "An edge in a connection."
|
10
10
|
field :node, wrapped_type, "The item at the end of the edge."
|
11
11
|
field :cursor, !types.String, "A cursor for use in pagination."
|
12
|
+
relay_node_type(wrapped_type)
|
12
13
|
block && instance_eval(&block)
|
13
14
|
end
|
14
15
|
end
|
@@ -4,15 +4,12 @@ module GraphQL
|
|
4
4
|
module EdgesInstrumentation
|
5
5
|
def self.instrument(type, field)
|
6
6
|
if field.edges?
|
7
|
-
edges_resolve = EdgesResolve.new(
|
8
|
-
|
9
|
-
resolve: field.resolve_proc,
|
10
|
-
lazy_resolve: field.lazy_resolve_proc,
|
11
|
-
)
|
7
|
+
edges_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.resolve_proc)
|
8
|
+
edges_lazy_resolve = EdgesResolve.new(edge_class: field.edge_class, resolve: field.lazy_resolve_proc)
|
12
9
|
|
13
10
|
field.redefine(
|
14
|
-
resolve: edges_resolve
|
15
|
-
lazy_resolve:
|
11
|
+
resolve: edges_resolve,
|
12
|
+
lazy_resolve: edges_lazy_resolve,
|
16
13
|
)
|
17
14
|
else
|
18
15
|
field
|
@@ -21,35 +18,22 @@ module GraphQL
|
|
21
18
|
|
22
19
|
|
23
20
|
class EdgesResolve
|
24
|
-
def initialize(edge_class:, resolve
|
21
|
+
def initialize(edge_class:, resolve:)
|
25
22
|
@edge_class = edge_class
|
26
23
|
@resolve_proc = resolve
|
27
|
-
@lazy_resolve_proc = lazy_resolve
|
28
24
|
end
|
29
25
|
|
30
26
|
# A user's custom Connection may return a lazy object,
|
31
27
|
# if so, handle it later.
|
32
|
-
def
|
28
|
+
def call(obj, args, ctx)
|
29
|
+
parent = ctx.object
|
33
30
|
nodes = @resolve_proc.call(obj, args, ctx)
|
34
31
|
if ctx.schema.lazy?(nodes)
|
35
|
-
|
32
|
+
nodes
|
36
33
|
else
|
37
|
-
|
34
|
+
nodes.map { |item| @edge_class.new(item, parent) }
|
38
35
|
end
|
39
36
|
end
|
40
|
-
|
41
|
-
# If we get this far, unwrap the wrapper,
|
42
|
-
# resolve the lazy object and make the edges as usual
|
43
|
-
def lazy_resolve(obj, args, ctx)
|
44
|
-
items = @lazy_resolve_proc.call(obj.lazy_object, args, ctx)
|
45
|
-
build_edges(items, obj.parent)
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def build_edges(items, connection)
|
51
|
-
items.map { |item| @edge_class.new(item, connection) }
|
52
|
-
end
|
53
37
|
end
|
54
38
|
end
|
55
39
|
end
|