Skip to content

Commit fe87d07

Browse files
authored
Merge pull request rmosolgo#4318 from rmosolgo/reduce-is-a
Reduce runtime .is_a? calls in schema objects
2 parents a97caff + a9a0fb2 commit fe87d07

7 files changed

Lines changed: 184 additions & 85 deletions

File tree

lib/graphql/schema/directive.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Schema
88
# - {.resolve}: Wraps field resolution (so it should call `yield` to continue)
99
class Directive < GraphQL::Schema::Member
1010
extend GraphQL::Schema::Member::HasArguments
11+
extend GraphQL::Schema::Member::HasArguments::HasDirectiveArguments
1112

1213
class << self
1314
# Directives aren't types, they don't have kinds.

lib/graphql/schema/field.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module GraphQL
66
class Schema
77
class Field
88
include GraphQL::Schema::Member::HasArguments
9+
include GraphQL::Schema::Member::HasArguments::FieldConfigured
910
include GraphQL::Schema::Member::HasAstNode
1011
include GraphQL::Schema::Member::HasPath
1112
include GraphQL::Schema::Member::HasValidators

lib/graphql/schema/member/has_arguments.rb

Lines changed: 102 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def self.included(cls)
1111
def self.extended(cls)
1212
cls.extend(ArgumentClassAccessor)
1313
cls.include(ArgumentObjectLoader)
14+
cls.extend(ClassConfigured)
1415
end
1516

1617
# @see {GraphQL::Schema::Argument#initialize} for parameters
@@ -109,14 +110,6 @@ def remove_argument(arg_defn)
109110

110111
# @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
111112
def arguments(context = GraphQL::Query::NullContext)
112-
inherited_arguments = if self.is_a?(Class) && superclass.respond_to?(:arguments)
113-
superclass.arguments(context)
114-
elsif defined?(@resolver_class) && @resolver_class
115-
@resolver_class.field_arguments(context)
116-
else
117-
nil
118-
end
119-
# Local definitions override inherited ones
120113
if own_arguments.any?
121114
own_arguments_that_apply = {}
122115
own_arguments.each do |name, args_entry|
@@ -125,70 +118,119 @@ def arguments(context = GraphQL::Query::NullContext)
125118
end
126119
end
127120
end
121+
# might be nil if there are actually no arguments
122+
own_arguments_that_apply || own_arguments
123+
end
128124

129-
if inherited_arguments
130-
if own_arguments_that_apply
131-
inherited_arguments.merge(own_arguments_that_apply)
132-
else
133-
inherited_arguments
125+
module ClassConfigured
126+
def inherited(child_class)
127+
super
128+
child_class.extend(InheritedArguments)
129+
end
130+
131+
module InheritedArguments
132+
def arguments(context = GraphQL::Query::NullContext)
133+
own_arguments = super
134+
inherited_arguments = superclass.arguments(context)
135+
136+
if own_arguments.any?
137+
if inherited_arguments.any?
138+
# Local definitions override inherited ones
139+
inherited_arguments.merge(own_arguments)
140+
else
141+
own_arguments
142+
end
143+
else
144+
inherited_arguments
145+
end
146+
end
147+
148+
def all_argument_definitions
149+
all_defns = {}
150+
ancestors.reverse_each do |ancestor|
151+
if ancestor.respond_to?(:own_arguments)
152+
all_defns.merge!(ancestor.own_arguments)
153+
end
154+
end
155+
all_defns = all_defns.values
156+
all_defns.flatten!
157+
all_defns
158+
end
159+
160+
161+
def get_argument(argument_name, context = GraphQL::Query::NullContext)
162+
warden = Warden.from_context(context)
163+
for ancestor in ancestors
164+
if ancestor.respond_to?(:own_arguments) &&
165+
(a = ancestor.own_arguments[argument_name]) &&
166+
(a = Warden.visible_entry?(:visible_argument?, a, context, warden))
167+
return a
168+
end
169+
end
170+
nil
134171
end
135-
else
136-
# might be nil if there are actually no arguments
137-
own_arguments_that_apply || own_arguments
138172
end
139173
end
140174

141-
def all_argument_definitions
142-
if self.is_a?(Class)
143-
all_defns = {}
144-
ancestors.reverse_each do |ancestor|
145-
if ancestor.respond_to?(:own_arguments)
146-
all_defns.merge!(ancestor.own_arguments)
175+
module FieldConfigured
176+
def arguments(context = GraphQL::Query::NullContext)
177+
own_arguments = super
178+
if defined?(@resolver_class) && @resolver_class
179+
inherited_arguments = @resolver_class.field_arguments(context)
180+
if own_arguments.any?
181+
if inherited_arguments.any?
182+
inherited_arguments.merge(own_arguments)
183+
else
184+
own_arguments
185+
end
186+
else
187+
inherited_arguments
147188
end
189+
else
190+
own_arguments
148191
end
149-
elsif defined?(@resolver_class) && @resolver_class
150-
all_defns = {}
151-
@resolver_class.all_field_argument_definitions.each do |arg_defn|
152-
key = arg_defn.graphql_name
153-
case (current_value = all_defns[key])
154-
when nil
155-
all_defns[key] = arg_defn
156-
when Array
157-
current_value << arg_defn
158-
when GraphQL::Schema::Argument
159-
all_defns[key] = [current_value, arg_defn]
160-
else
161-
raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
192+
end
193+
194+
def all_argument_definitions
195+
if defined?(@resolver_class) && @resolver_class
196+
all_defns = {}
197+
@resolver_class.all_field_argument_definitions.each do |arg_defn|
198+
key = arg_defn.graphql_name
199+
case (current_value = all_defns[key])
200+
when nil
201+
all_defns[key] = arg_defn
202+
when Array
203+
current_value << arg_defn
204+
when GraphQL::Schema::Argument
205+
all_defns[key] = [current_value, arg_defn]
206+
else
207+
raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
208+
end
162209
end
210+
all_defns.merge!(own_arguments)
211+
all_defns = all_defns.values
212+
all_defns.flatten!
213+
all_defns
214+
else
215+
super
163216
end
164-
all_defns.merge!(own_arguments)
165-
else
166-
all_defns = own_arguments
167217
end
168-
all_defns = all_defns.values
218+
end
219+
220+
def all_argument_definitions
221+
all_defns = own_arguments.values
169222
all_defns.flatten!
170223
all_defns
171224
end
172225

173226
# @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
174227
def get_argument(argument_name, context = GraphQL::Query::NullContext)
175228
warden = Warden.from_context(context)
176-
if !self.is_a?(Class)
177-
if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
178-
visible_arg
179-
elsif defined?(@resolver_class) && @resolver_class
180-
@resolver_class.get_field_argument(argument_name, context)
181-
else
182-
nil
183-
end
229+
if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
230+
visible_arg
231+
elsif defined?(@resolver_class) && @resolver_class
232+
@resolver_class.get_field_argument(argument_name, context)
184233
else
185-
for ancestor in ancestors
186-
if ancestor.respond_to?(:own_arguments) &&
187-
(a = ancestor.own_arguments[argument_name]) &&
188-
(a = Warden.visible_entry?(:visible_argument?, a, context, warden))
189-
return a
190-
end
191-
end
192234
nil
193235
end
194236
end
@@ -265,7 +307,12 @@ def coerce_arguments(parent_object, values, context, &block)
265307
# but not for directives.
266308
# TODO apply static validations on schema definitions?
267309
def validate_directive_argument(arg_defn, value)
268-
if arg_defn.owner.is_a?(Class) && arg_defn.owner < GraphQL::Schema::Directive
310+
# this is only implemented on directives.
311+
nil
312+
end
313+
314+
module HasDirectiveArguments
315+
def validate_directive_argument(arg_defn, value)
269316
if value.nil? && arg_defn.type.non_null?
270317
raise ArgumentError, "#{arg_defn.path} is required, but no value was given"
271318
end

lib/graphql/schema/member/has_interfaces.rb

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,37 @@ def own_interface_type_memberships
5555
end
5656

5757
def interface_type_memberships
58-
own_tms = own_interface_type_memberships
59-
if (self.is_a?(Class) && superclass.respond_to?(:interface_type_memberships))
60-
inherited_tms = superclass.interface_type_memberships
61-
if inherited_tms.size > 0
62-
own_tms + inherited_tms
63-
else
64-
own_tms
58+
own_interface_type_memberships
59+
end
60+
61+
module ClassConfigured
62+
# This combination of extended -> inherited -> extended
63+
# means that the base class (`Schema::Object`) *won't*
64+
# have the superclass-related code in `InheritedInterfaces`,
65+
# but child classes of `Schema::Object` will have it.
66+
# That way, we don't need a `superclass.respond_to?(...)` check.
67+
def inherited(child_class)
68+
super
69+
child_class.extend(InheritedInterfaces)
70+
end
71+
72+
module InheritedInterfaces
73+
def interfaces(context = GraphQL::Query::NullContext)
74+
visible_interfaces = super
75+
visible_interfaces.concat(superclass.interfaces(context))
76+
visible_interfaces.uniq!
77+
visible_interfaces
78+
end
79+
80+
def interface_type_memberships
81+
own_tms = super
82+
inherited_tms = superclass.interface_type_memberships
83+
if inherited_tms.size > 0
84+
own_tms + inherited_tms
85+
else
86+
own_tms
87+
end
6588
end
66-
else
67-
own_tms
6889
end
6990
end
7091

@@ -73,28 +94,29 @@ def interfaces(context = GraphQL::Query::NullContext)
7394
warden = Warden.from_context(context)
7495
visible_interfaces = []
7596
own_interface_type_memberships.each do |type_membership|
76-
# During initialization, `type_memberships` can hold late-bound types
7797
case type_membership
78-
when String, Schema::LateBoundType
79-
visible_interfaces << type_membership
8098
when Schema::TypeMembership
8199
if warden.visible_type_membership?(type_membership, context)
82100
visible_interfaces << type_membership.abstract_type
83101
end
102+
when String, Schema::LateBoundType
103+
# During initialization, `type_memberships` can hold late-bound types
104+
visible_interfaces << type_membership
84105
else
85106
raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
86107
end
87108
end
109+
visible_interfaces.uniq!
88110

89-
if self.is_a?(Class) && superclass <= GraphQL::Schema::Object
90-
visible_interfaces.concat(superclass.interfaces(context))
91-
end
92-
93-
visible_interfaces.uniq
111+
visible_interfaces
94112
end
95113

96114
private
97115

116+
def self.extended(child_class)
117+
child_class.extend(ClassConfigured)
118+
end
119+
98120
def inherited(subclass)
99121
super
100122
subclass.class_eval do

lib/graphql/schema/member/has_validators.rb

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,38 @@ def validates(validation_config)
1818

1919
# @return [Array<GraphQL::Schema::Validator>]
2020
def validators
21-
own_validators = @own_validators || EMPTY_ARRAY
22-
if self.is_a?(Class) && superclass.respond_to?(:validators) && (inherited_validators = superclass.validators).any?
23-
inherited_validators + own_validators
24-
else
25-
own_validators
21+
@own_validators || EMPTY_ARRAY
22+
end
23+
24+
module ClassConfigured
25+
def inherited(child_cls)
26+
super
27+
child_cls.extend(ClassValidators)
2628
end
29+
30+
module ClassValidators
31+
include Schema::FindInheritedValue::EmptyObjects
32+
33+
def validators
34+
inherited_validators = superclass.validators
35+
if inherited_validators.any?
36+
if @own_validators.nil?
37+
inherited_validators
38+
else
39+
inherited_validators + @own_validators
40+
end
41+
elsif @own_validators.nil?
42+
EMPTY_ARRAY
43+
else
44+
@own_validators
45+
end
46+
end
47+
end
48+
end
49+
50+
def self.extended(child_cls)
51+
super
52+
child_cls.extend(ClassConfigured)
2753
end
2854
end
2955
end

spec/graphql/schema/interface_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ def thing
336336
end
337337
interfaces_names = thing_type["interfaces"].map { |i| i["name"] }.sort
338338

339-
assert_equal interfaces_names, ["Named", "Node", "Timestamped"]
339+
assert_equal ["Named", "Node", "Timestamped"], interfaces_names
340340
end
341341
end
342342

spec/graphql/schema/object_spec.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@
4040
# one more than the parent class
4141
assert_equal 10, new_object_class.fields.size
4242
# inherited interfaces are present
43-
assert_equal [
44-
"GloballyIdentifiable",
45-
"HasMusicians",
46-
"InvisibleNameEntity",
47-
"NamedEntity",
48-
"PrivateNameEntity",
49-
], new_object_class.interfaces.map(&:graphql_name).sort
43+
expected_interface_names = [
44+
"GloballyIdentifiable",
45+
"HasMusicians",
46+
"InvisibleNameEntity",
47+
"NamedEntity",
48+
"PrivateNameEntity",
49+
]
50+
assert_equal expected_interface_names, object_class.interfaces.map(&:graphql_name).sort
51+
assert_equal expected_interface_names, new_object_class.interfaces.map(&:graphql_name).sort
5052
# The new field is present
5153
assert new_object_class.fields.key?("newField")
5254
# The overridden field is present:

0 commit comments

Comments
 (0)