Module: ElasticGraph::SchemaDefinition::Mixins::HasIndices

Included in:
SchemaElements::InterfaceType, SchemaElements::ObjectType, SchemaElements::UnionType
Defined in:
lib/elastic_graph/schema_definition/mixins/has_indices.rb

Overview

Provides APIs for defining datastore indices.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#default_graphql_resolver::Symbol? (readonly)

Returns the default GraphQL resolver to use for fields on this type.

Returns:

  • (::Symbol, nil)

    the default GraphQL resolver to use for fields on this type



23
24
25
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 23

def default_graphql_resolver
  @default_graphql_resolver
end

#runtime_metadata_overridesObject (readonly)



20
21
22
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 20

def 
  @runtime_metadata_overrides
end

Instance Method Details

#abstract?Boolean

Abstract types are rare, so return false. This can be overridden in the host class.

Returns:

  • (Boolean)


175
176
177
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 175

def abstract?
  false
end

#derive_indexed_type_fields(name, from_id:, route_with: nil, rollover_with: nil) {|Indexing::DerivedIndexedType| ... } ⇒ void

This method returns an undefined value.

Configures the ElasticGraph indexer to derive another type from this indexed type, using the ‘from_id` field as the source of the `id` of the derived type, and the provided block for the definitions of the derived fields.

Examples:

Derive a ‘Course` type from `StudentCourseEnrollment` events

ElasticGraph.define_schema do |schema|
  # `StudentCourseEnrollment` is a directly indexed type.
  schema.object_type "StudentCourseEnrollment" do |t|
    t.field "id", "ID"
    t.field "courseId", "ID"
    t.field "courseName", "String"
    t.field "studentName", "String"
    t.field "courseStartDate", "Date"

    t.index "student_course_enrollments"

    # Here we define how the `Course` indexed type  is derived when we index `StudentCourseEnrollment` events.
    t.derive_indexed_type_fields "Course", from_id: "courseId" do |derive|
      # `derive` is an instance of `DerivedIndexedType`.
      derive.immutable_value "name", from: "courseName"
      derive.append_only_set "students", from: "studentName"
      derive.min_value "firstOfferedDate", from: "courseStartDate"
      derive.max_value "mostRecentlyOfferedDate", from: "courseStartDate"
    end
  end

  # `Course` is an indexed type that is derived entirely from `StudentCourseEnrollment` events.
  schema.object_type "Course" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.field "students", "[String!]!"
    t.field "firstOfferedDate", "Date"
    t.field "mostRecentlyOfferedDate", "Date"

    t.index "courses"
  end
end

Parameters:

  • name (String)

    name of the derived type

  • from_id (String)

    path to the source type field with ‘id` values for the derived type

  • route_with (String, nil) (defaults to: nil)

    path to the source type field with values for shard routing on the derived type

  • rollover_with (String, nil) (defaults to: nil)

    path to the source type field with values for index rollover on the derived type

Yields:



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 222

def derive_indexed_type_fields(
  name,
  from_id:,
  route_with: nil,
  rollover_with: nil,
  &block
)
  Indexing::DerivedIndexedType.new(
    source_type: self,
    destination_type_ref: schema_def_state.type_ref(name).to_final_form,
    id_source: from_id,
    routing_value_source: route_with,
    rollover_timestamp_value_source: rollover_with,
    &block
  ).tap { |dit| derived_indexed_types << dit }
end

#derived_indexed_typesArray<Indexing::DerivedIndexedType>

Returns list of derived types for this source type.

Returns:



240
241
242
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 240

def derived_indexed_types
  @derived_indexed_types ||= []
end

#directly_queryable?Boolean

Note:

A concrete subtype that inherits an index from an abstract parent is NOT directly queryable on its own —only the abstract type that declared the index is. Use #root_document_type? to check whether a type is stored at the root of any index (own or inherited).

Returns true if this type is directly queryable via a type-specific field on the root ‘Query` type.

Returns:

  • (Boolean)

    true if this type is directly queryable via a type-specific field on the root ‘Query` type.



168
169
170
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 168

def directly_queryable?
  has_own_index_def?
end

#fields_with_sourcesObject



324
325
326
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 324

def fields_with_sources
  indexing_fields_by_name_in_index.values.reject { |f| f.source.nil? }
end

#has_own_index_def?Boolean

Returns true if this type has its own index definition (not inherited from an abstract parent).

Returns:

  • (Boolean)

    true if this type has its own index definition (not inherited from an abstract parent)



129
130
131
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 129

def has_own_index_def?
  !@own_index_def.nil?
end

#index(name, **settings) {|Indexing::Index| ... } ⇒ void

Note:

Use #root_query_fields on indexed types to name the field that will be exposed on ‘Query`.

Note:

Indexed types must also define an ‘id` field, which ElasticGraph will use as the primary key. When an abstract type declares the index, each concrete subtype must also define `id`.

Note:

Datastore index settings can also be defined (or overridden) in an environment-specific settings YAML file. Index settings that you want to configure differently for different environments (such as ‘index.number_of_shards`—-production and staging will probably need different numbers!) should be configured in the per-environment YAML configuration files rather than here.

This method returns an undefined value.

Declares a datastore index for the current type, converting it from an embedded type to an indexed type that is directly queryable from the root ‘Query` type. When called on an abstract `interface_type` or `union_type`, concrete subtypes inherit the index by default — they share the same datastore index without needing to call `t.index` themselves. A subtype can opt out of this shared index inheritance by calling `t.index` with a different name to use a dedicated index instead.

Examples:

Define a ‘campaigns` index on a concrete type

ElasticGraph.define_schema do |schema|
  schema.object_type "Campaign" do |t|
    t.field "id", "ID"

    t.index(
      "campaigns",
      # Configure `index.refresh_interval`.
      refresh_interval: "1s",
      # Use `index.search` to log warnings for any search query that take more than five seconds.
      search: {slowlog: {level: "WARN", threshold: {query: {warn: "5s"}}}}
    ) do |i|
      # The index can be customized further here.
    end
  end
end

Declare a shared index on an interface

ElasticGraph.define_schema do |schema|
  schema.interface_type "Vehicle" do |t|
    t.field "id", "ID"
    t.field "make", "String"
    t.index "vehicles"
  end

  schema.object_type "Car" do |t|
    t.implements "Vehicle"
    t.field "id", "ID"
    t.field "make", "String"
    t.field "numDoors", "Int"
    # Inherits the `vehicles` index — no need to call `t.index`.
  end

  schema.object_type "Motorcycle" do |t|
    t.implements "Vehicle"
    t.field "id", "ID"
    t.field "make", "String"
    t.field "engineCC", "Int"
    # Opts out of the shared index and gets its own dedicated index instead.
    t.index "motorcycles"
  end
end

Parameters:

Yields:



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 94

def index(name, **settings, &block)
  unless @can_configure_index
    raise Errors::SchemaError, "Cannot define an index on `#{self.name}` after initialization is complete. " \
      "Indices must be configured during initial type definition."
  end

  if @own_index_def
    raise Errors::SchemaError, "Cannot define multiple indices on `#{self.name}`. " \
      "Only one index per type is supported. An index named `#{@own_index_def.name}` has already been defined."
  end

  schema_def_state.register_index(name, self)
  @own_index_def = schema_def_state.factory.new_index(name, settings, self, &block)
end

#index_defIndexing::Index?

Resolves this type’s index definition. This will be one of:

  • This type’s own_index_def (if it directly defines an index)

  • An inherited index from an abstract supertype (union/interface) that has an index

This type can be a subtype of multiple abstract types (e.g., implements multiple interfaces), but unless it defines its own index, at most one of its supertypes may have an index. If multiple parent types are indexed, this method raises an error to prevent ambiguity about which index to inherit.

Returns:

  • (Indexing::Index, nil)

    the index definition, or nil if this type has no index

Raises:

  • (Errors::SchemaError)

    if this type is a subtype of multiple indexed abstract types



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 143

def index_def
  return own_index_def if has_own_index_def?

  indexed_supertypes = recursively_resolve_supertypes.select(&:has_own_index_def?)

  if indexed_supertypes.size > 1
    parent_names = indexed_supertypes.map { |p| p.own_index_def.name }.join(", ")
    raise Errors::SchemaError,
      "The `#{name}` type is a subtype of multiple indexed abstract types (#{parent_names}). " \
      "If a concrete type does not define an index, it may not be a member of multiple indexed abstract types."
  end

  indexed_supertypes.first&.own_index_def
end

#initialize(*args, **options) ⇒ Object



27
28
29
30
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 27

def initialize(*args, **options)
  super(*args, **options)
  initialize_has_indices { yield self }
end

#override_runtime_metadata(**overrides) ⇒ void

This method returns an undefined value.

Configures overrides for runtime metadata. The provided runtime metadata values will be persisted in the ‘runtime_metadata.yaml` schema artifact and made available at runtime to `elasticgraph-graphql` and `elasticgraph-indexer`.



249
250
251
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 249

def (**overrides)
  @runtime_metadata_overrides.merge!(overrides)
end

#own_index_defIndexing::Index?

Returns the index definition directly defined on this type, or nil if no index is defined directly. This will be nil when a type is inheriting an index definition from an abstract parent type.

Returns:

  • (Indexing::Index, nil)

    the index definition directly defined on this type, or nil if no index is defined directly. This will be nil when a type is inheriting an index definition from an abstract parent type.



124
125
126
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 124

def own_index_def
  @own_index_def
end

#plural_root_query_field_nameString

Returns the plural name of the entity; used for the root ‘Query` field that queries documents of this indexed type.

Returns:

  • (String)

    the plural name of the entity; used for the root ‘Query` field that queries documents of this indexed type



307
308
309
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 307

def plural_root_query_field_name
  @plural_root_query_field_name || naively_pluralize_type_name(name)
end

#resolve_fields_with(default_resolver_name, **config) ⇒ void

This method returns an undefined value.

Configures the default GraphQL resolver that will be used to resolve the fields of this type. Individual fields can override this using SchemaElements::Field#resolve_with.

Parameters:

  • default_resolver_name (Symbol)

    name of the GraphQL resolver to use as the default for fields of this type

  • config (Hash<Symbol, Object>)

    configuration parameters for the resolver

See Also:



116
117
118
119
120
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 116

def resolve_fields_with(default_resolver_name, **config)
  @default_graphql_resolver = default_resolver_name&.then do
    SchemaArtifacts::RuntimeMetadata::ConfiguredGraphQLResolver.new(it, config)
  end
end

#root_document_type?Boolean

Returns true if this type is a root document type that lives at a document root in the datastore (is indexed). This returns true for types with their own index definition or types that inherit an index from a supertype.

Returns:

  • (Boolean)

    true if this type is a root document type that lives at a document root in the datastore (is indexed). This returns true for types with their own index definition or types that inherit an index from a supertype.



160
161
162
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 160

def root_document_type?
  !index_def.nil?
end

#root_query_fields(plural:, singular: nil) {|SchemaElements::Field| ... } ⇒ void

This method returns an undefined value.

Determines what the root ‘Query` fields will be to query this indexed type. In addition, this method accepts a block, which you can use to customize the root query field (such as adding a GraphQL directive to it).

Examples:

Set ‘plural` and `singular` names

ElasticGraph.define_schema do |schema|
  schema.object_type "Person" do |t|
    t.field "id", "ID"

    # Results in `Query.people` and `Query.personAggregations`.
    t.root_query_fields plural: "people", singular: "person"

    t.index "people"
  end
end

Customize ‘Query` fields

ElasticGraph.define_schema do |schema|
  schema.object_type "Person" do |t|
    t.field "id", "ID"

    t.root_query_fields plural: "people", singular: "person" do |f|
      # Marks `Query.people` and `Query.personAggregations` as deprecated.
      f.directive "deprecated"
    end

    t.index "people"
  end
end

Parameters:

  • plural (String)

    the plural name of the entity; used for the root ‘Query` field that queries documents of this indexed type

  • singular (String, nil) (defaults to: nil)

    the singular name of the entity; used for the root ‘Query` field (with an `Aggregations` suffix) that queries aggregations of this indexed type. If not provided, will derive it from the type name (e.g. converting it to `camelCase` or `snake_case`, depending on configuration).

Yields:

  • (SchemaElements::Field)

    field on the root ‘Query` type used to query this indexed type, to support customization



300
301
302
303
304
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 300

def root_query_fields(plural:, singular: nil, &customization_block)
  @plural_root_query_field_name = plural
  @singular_root_query_field_name = singular
  @root_query_fields_customizations = customization_block
end

#root_query_fields_customizationsObject



319
320
321
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 319

def root_query_fields_customizations
  @root_query_fields_customizations
end

#runtime_metadata(extra_update_targets) ⇒ Object



254
255
256
257
258
259
260
261
262
263
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 254

def (extra_update_targets)
  SchemaArtifacts::RuntimeMetadata::ObjectType.new(
    update_targets: derived_indexed_types.map(&:runtime_metadata_for_source_type) + [self_update_target].compact + extra_update_targets,
    index_definition_names: [index_def&.name].compact,
    graphql_fields_by_name: ,
    elasticgraph_category: nil,
    source_type: nil,
    graphql_only_return_type: graphql_only?
  ).with(**)
end

#singular_root_query_field_nameString

Returns the singular name of the entity; used for the root ‘Query` field (with an `Aggregations` suffix) that queries aggregations of this indexed type. If not provided, will derive it from the type name (e.g. converting it to `camelCase` or `snake_case`, depending on configuration).

Returns:

  • (String)

    the singular name of the entity; used for the root ‘Query` field (with an `Aggregations` suffix) that queries aggregations of this indexed type. If not provided, will derive it from the type name (e.g. converting it to `camelCase` or `snake_case`, depending on configuration).



314
315
316
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 314

def singular_root_query_field_name
  @singular_root_query_field_name || to_field_name(name)
end

#source_excludes_paths(path_prefix = "", under_non_returnable_parent = false) ⇒ Object

Returns the list of ‘_source.excludes` paths for non-returnable, non-highlightable fields.

Hidden highlightable fields must remain in ‘_source` so the datastore can still produce search highlight snippets for them.

Uses ‘indexing_fields_by_name_in_index` for traversal (same as `index_field_runtime_metadata_tuples`) to avoid infinite recursion through interface/union subtype cycles.



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/elastic_graph/schema_definition/mixins/has_indices.rb', line 338

def source_excludes_paths(path_prefix = "", under_non_returnable_parent = false)
  indexing_fields_by_name_in_index.flat_map do |name, field|
    path = path_prefix + name
    object_type = field.type.fully_unwrapped.as_object_type
    non_returnable = under_non_returnable_parent || !field.returnable?

    if object_type
      if non_returnable && !field.highlightable?
        ["#{path}.*"]
      else
        object_type.source_excludes_paths("#{path}.", non_returnable)
      end
    elsif non_returnable && !field.highlightable?
      [path]
    else
      []
    end
  end
end