Skip to content

Commit 9732147

Browse files
Address PR review feedback on GraphQL::Schema::Type helpers
- `source_type`: restore `&.` safe navigation since `@object_runtime_metadata` can be nil for non-object types (scalars, enums); return `nil` instead of `self` for non-derived types; update `search_index_definitions` to branch on `source_type` truthy rather than `indexed_aggregation?`, and update comment to describe the general case with indexed aggregation as a prime example - `subtypes`: return a `Set` instead of `Array`; inline `subtypes` in `non_subtypes_in_shared_index` rather than caching in a local variable - Tests: use `before(:context)` shared schema in `#source_type` group; add scalar and enum nil cases; use `contain_exactly` for precise set assertions; consolidate `not_to include` calls; add coverage for `t == self` guard Generated with Claude Code
1 parent 7857f68 commit 9732147

3 files changed

Lines changed: 48 additions & 47 deletions

File tree

  • elasticgraph-graphql

elasticgraph-graphql/lib/elastic_graph/graphql/schema/type.rb

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ def name
6161
# List of index definitions that should be searched for this type.
6262
def search_index_definitions
6363
@search_index_definitions ||=
64-
if indexed_aggregation?
65-
# For an indexed aggregation, we just delegate to its source type. This works better than
66-
# dumping index definitions in the runtime metadata of the indexed aggregation type itself
67-
# because of abstract (interface/union) types. The source document type handles that (since
68-
# there is a supertype/subtype relationship on the document types) but that relationship
69-
# does not exist on the indexed aggregation.
64+
if (st = source_type)
65+
# When a type has a source type (a prime example being indexed aggregations), we delegate
66+
# to the source type. This works better than dumping index definitions in the runtime metadata
67+
# of the derived type itself because of abstract (interface/union) types. The source document
68+
# type handles that (since there is a supertype/subtype relationship on the document types)
69+
# but that relationship does not exist on derived types.
7070
#
7171
# For example, assume we have these indexed document types:
7272
# - type Person {}
@@ -76,7 +76,7 @@ def search_index_definitions
7676
# We can go from `Inventor` to its subtypes to find the search indexes. However, `InventorAggregation`
7777
# is NOT a union of `PersonAggregation` and `CompanyAggregation`, so we can't do the same thing on the
7878
# indexed aggregation types. Delegating to the source type solves this case.
79-
source_type.search_index_definitions
79+
st.search_index_definitions
8080
else
8181
@index_definitions.union(subtypes.flat_map(&:search_index_definitions))
8282
end
@@ -123,18 +123,15 @@ def subtypes
123123
@subtypes ||= @schema
124124
.graphql_schema
125125
.possible_types(graphql_type, visibility_profile: :boot)
126-
.map { |t| @schema.type_from(t) } - [self]
126+
.map { |t| @schema.type_from(t) }
127+
.reject { |t| t == self }
128+
.to_set
127129
end
128130

129131
# For derived types (e.g. indexed aggregations), returns the underlying source document type.
130-
# For non-derived types, returns `self`.
132+
# Returns `nil` for non-derived types.
131133
def source_type
132-
@source_type ||=
133-
if (st = @object_runtime_metadata.source_type)
134-
@schema.type_named(st)
135-
else
136-
self
137-
end
134+
@source_type ||= @object_runtime_metadata&.source_type&.then { |st| @schema.type_named(st) }
138135
end
139136

140137
# Returns the set of concrete indexed document types that share any of this type's search
@@ -145,13 +142,11 @@ def source_type
145142
# with a concrete `__typename`. When filtering by `__typename`, only concrete types are
146143
# relevant.
147144
def non_subtypes_in_shared_index
148-
@non_subtypes_in_shared_index ||= begin
149-
all_subtypes = subtypes.to_set # all concrete subtypes at any depth
145+
@non_subtypes_in_shared_index ||=
150146
search_index_definitions
151147
.flat_map { |index_def| @schema.document_types_stored_in(index_def.name).to_a }
152-
.reject { |t| t == self || all_subtypes.include?(t) || t.abstract? }
148+
.reject { |t| t == self || subtypes.include?(t) || t.abstract? }
153149
.to_set
154-
end
155150
end
156151

157152
def field_named(field_name)

elasticgraph-graphql/sig/elastic_graph/graphql/schema/type.rbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ module ElasticGraph
99
attr_reader graphql_type: ::GraphQL::Schema::_Type
1010
attr_reader grouping_missing_value_placeholder: ::String? | ::Numeric?
1111
def search_index_definitions: () -> ::Array[DatastoreCore::_IndexDefinition]
12-
def source_type: () -> Type
13-
def subtypes: () -> ::Array[Type]
12+
def source_type: () -> Type?
13+
def subtypes: () -> ::Set[Type]
1414
def non_subtypes_in_shared_index: () -> ::Set[Type]
1515
def unwrap_fully: () -> Type
1616
def field_named: (::String) -> Field

elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/type_spec.rb

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -604,14 +604,14 @@ def type_for(field_name)
604604
end
605605
end
606606

607-
it "returns [] for object types" do
607+
it "returns an empty set for object types" do
608608
type = schema.type_named("Size")
609-
expect(type.subtypes).to eq []
609+
expect(type.subtypes).to be_empty
610610
end
611611

612-
it "returns [] for scalar types" do
612+
it "returns an empty set for scalar types" do
613613
type = schema.type_named("Int")
614-
expect(type.subtypes).to eq []
614+
expect(type.subtypes).to be_empty
615615
end
616616

617617
it "returns the subtypes of a union" do
@@ -759,28 +759,37 @@ def search_index_definitions_from(type_name: "TheType")
759759
end
760760

761761
describe "#source_type" do
762-
it "returns the underlying document type for an indexed aggregation type" do
763-
schema = define_schema do |s|
762+
attr_reader :schema
763+
764+
before(:context) do
765+
@schema = define_schema(clients_by_name: {}) do |s|
766+
s.enum_type "Status" do |t|
767+
t.value "ACTIVE"
768+
t.value "INACTIVE"
769+
end
770+
764771
s.object_type "Thing" do |t|
765772
t.field "id", "ID!"
773+
t.field "status", "Status"
766774
t.index "things"
767775
end
768776
end
777+
end
769778

770-
aggregation_type = schema.type_named("ThingAggregation")
771-
expect(aggregation_type.source_type).to be schema.type_named("Thing")
779+
it "returns the underlying document type for an indexed aggregation type" do
780+
expect(schema.type_named("ThingAggregation").source_type).to be schema.type_named("Thing")
772781
end
773782

774-
it "returns self for a non-derived type" do
775-
schema = define_schema do |s|
776-
s.object_type "Thing" do |t|
777-
t.field "id", "ID!"
778-
t.index "things"
779-
end
780-
end
783+
it "returns nil for a non-derived object type" do
784+
expect(schema.type_named("Thing").source_type).to be_nil
785+
end
781786

782-
thing_type = schema.type_named("Thing")
783-
expect(thing_type.source_type).to be thing_type
787+
it "returns nil for a scalar type" do
788+
expect(schema.type_named("Int").source_type).to be_nil
789+
end
790+
791+
it "returns nil for an enum type" do
792+
expect(schema.type_named("Status").source_type).to be_nil
784793
end
785794
end
786795

@@ -821,17 +830,14 @@ def search_index_definitions_from(type_name: "TheType")
821830
end
822831
end
823832

824-
it "excludes the type itself and its subtypes" do
825-
store = schema.type_named("Store")
826-
827-
result = store.non_subtypes_in_shared_index
828-
expect(result).not_to include(store)
829-
expect(result).not_to include(schema.type_named("OnlineStore"))
830-
expect(result).not_to include(schema.type_named("PhysicalStore"))
833+
it "excludes the type itself and its subtypes, returning only concrete sibling types in the shared index" do
834+
expect(schema.type_named("Store").non_subtypes_in_shared_index).to contain_exactly(schema.type_named("Wholesaler"))
831835
end
832836

833-
it "includes concrete sibling types that share the same index" do
834-
expect(schema.type_named("Store").non_subtypes_in_shared_index).to include(schema.type_named("Wholesaler"))
837+
it "excludes the type itself even when the type is a concrete type in a shared index" do
838+
# Wholesaler is a concrete type stored in the "channels" index, so it appears in
839+
# document_types_stored_in("channels"). Without the `t == self` guard it would include itself.
840+
expect(schema.type_named("Wholesaler").non_subtypes_in_shared_index).to contain_exactly(schema.type_named("OnlineStore"))
835841
end
836842

837843
it "returns an empty set when all types sharing its indexes are subtypes" do

0 commit comments

Comments
 (0)