Class: ElasticGraph::SchemaDefinition::SchemaElements::TypeWithSubfields Abstract

Inherits:
Struct
  • Object
show all
Includes:
Mixins::CanBeGraphQLOnly, Mixins::HasDerivedGraphQLTypeCustomizations, Mixins::HasDirectives, Mixins::HasDocumentation, Mixins::HasTypeInfo, Mixins::VerifiesGraphQLName
Defined in:
elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb

Overview

This class is abstract.

Defines common functionality for all GraphQL types that have subfields:

Direct Known Subclasses

InterfaceType, ObjectType

Constant Summary

Constants included from Mixins::HasTypeInfo

Mixins::HasTypeInfo::CUSTOMIZABLE_DATASTORE_PARAMS

Instance Attribute Summary

Attributes included from Mixins::HasDocumentation

#doc_comment

Instance Method Summary collapse

Methods included from Mixins::HasTypeInfo

#json_schema, #json_schema_options, #mapping, #mapping_options

Methods included from Mixins::HasDerivedGraphQLTypeCustomizations

#customize_derived_type_fields, #customize_derived_types

Methods included from Mixins::HasDirectives

#directive, #directives, #directives_sdl

Methods included from Mixins::HasDocumentation

#append_to_documentation, #derived_documentation, #documentation, #formatted_documentation

Methods included from Mixins::CanBeGraphQLOnly

#graphql_only, #graphql_only?

Methods included from Mixins::VerifiesGraphQLName

verify_name!

Instance Method Details

#deleted_field(field_name) ⇒ void

Note:

In situations where this API applies, ElasticGraph will give you an error message indicating that you need to use this API or Field#renamed_from. Likewise, when ElasticGraph no longer needs to know about this, it’ll give you a warning indicating the call to this method can be removed.

This method returns an undefined value.

Registers the name of a field that existed in a prior version of the schema but has been deleted.

Examples:

Indicate that Widget.description has been deleted

ElasticGraph.define_schema do |schema|
  schema.object_type "Widget" do |t|
    t.deleted_field "description"
  end
end

Parameters:

  • field_name (String)

    name of field that used to exist but has been deleted



238
239
240
241
242
243
244
245
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 238

def deleted_field(field_name)
  schema_def_state.register_deleted_field(
    name,
    field_name,
    defined_at: caller_locations(2, 1).first, # : ::Thread::Backtrace::Location
    defined_via: %(type.deleted_field "#{field_name}")
  )
end

#field(name, type, graphql_only: false, indexing_only: false, **options) {|Field| ... } ⇒ void

Note:

Be careful about defining non-nullable fields. Changing a field’s type from non-nullable (e.g. Int!) to nullable (e.g. Int) is a breaking change for clients. Making a field non-nullable may also prevent you from applying permissioning to a field via an AuthZ layer (as such a layer would have no way to force a field value to null when for a client denied field access). Therefore, we recommend limiting your use of ! to only a few situations such as defining a type’s primary key (e.g. t.field "id", "ID!") or defining a list field (e.g. t.field "authors", "[String!]!") since empty lists already provide a “no data” representation. You can still configure the ElasticGraph indexer to require a non-null value for a field using f.json_schema nullable: false.

Note:

ElasticGraph’s understanding of datastore capabilities may override your configured aggregatable/filterable/groupable/sortable options. For example, a field indexed as text for full text search will not be sortable or groupable even if you pass sortable: true, groupable: true when defining the field, because text fields cannot be efficiently sorted by or grouped on.

This method returns an undefined value.

Defines a GraphQL field on this type.

Examples:

Define a field with documentation

ElasticGraph.define_schema do |schema|
  schema.object_type "Campaign" do |t|
    t.field "id", "ID" do |f|
      f.documentation "The Campaign's identifier."
    end
  end
end

Omit a new field from the GraphQL schema until its data has been backfilled

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

    # TODO: remove `indexing_only: true` once the data for this field has been fully backfilled
    t.field "endDate", "Date", indexing_only: true
  end
end

Use graphql_only to introduce a new name for an existing field

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

    t.field "endOn", "Date" do |f|
      f.directive "deprecated", reason: "Use `endDate` instead."
    end

    # We've decided we want to call the field `endDate` instead of `endOn`, but the data
    # for this field is currently indexed in `endOn`, so we can use `graphql_only` and
    # `name_in_index` to expose the existing data under a new field name.
    t.field "endDate", "Date", name_in_index: "endOn", graphql_only: true
  end
end

Parameters:

  • name (String)

    name of the field

  • type (String)

    type of the field as a type reference. The named type must be one of ElasticGraph’s built-in types or a type that has been defined in your schema.

  • graphql_only (Boolean) (defaults to: false)

    if true, ElasticGraph will define the field as a GraphQL field but omit it from the indexing artifacts (json_schemas.yaml and datastore_config.yaml). This can be used along with name_in_index to support careful schema evolution.

  • indexing_only (Boolean) (defaults to: false)

    if true, ElasticGraph will define the field for indexing (in the json_schemas.yaml and datastore_config.yaml schema artifact) but will omit it from the GraphQL schema. This can be useful to begin indexing a field before you expose it in GraphQL so that you can fully backfill it first.

  • options (Hash)

    a customizable set of options

Options Hash (**options):

  • name_in_index (String)

    the name of the field in the datastore index. Can be used to back a GraphQL field with a differently named field in the index.

  • singular (String)

    can be used on a list field (e.g. t.field "tags", "[String!]!", singular: "tag") to tell ElasticGraph what the singular form of a field’s name is. When provided, ElasticGraph will define a groupedBy field (using the singular form) allowing clients to group by individual values from the field.

  • aggregatable (Boolean)

    force-enables or disables the ability for aggregation queries to aggregate over this field. When not provided, ElasticGraph will infer field aggregatability based on the field’s GraphQL type and mapping type.

  • filterable (Boolean)

    force-enables or disables the ability for queries to filter by this field. When not provided, ElasticGraph will infer field filterability based on the field’s GraphQL type and mapping type.

  • groupable (Boolean)

    force-enables or disables the ability for aggregation queries to group by this field. When not provided, ElasticGraph will infer field groupability based on the field’s GraphQL type and mapping type.

  • sortable (Boolean)

    force-enables or disables the ability for queries to sort by this field. When not provided, ElasticGraph will infer field sortability based on the field’s GraphQL type and mapping type.

Yields:

  • (Field)

    the field for further customization

See Also:



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 192

def field(name, type, graphql_only: false, indexing_only: false, **options)
  if reserved_field_names.include?(name)
    raise Errors::SchemaError, "Invalid field name: `#{self.name}.#{name}`. `#{name}` is reserved for use by " \
      "ElasticGraph as a filtering operator. To use it for a field name, add " \
      "the `schema_element_name_overrides` option (on `ElasticGraph::SchemaDefinition::RakeTasks.new`) to " \
      "configure an alternate name for the `#{name}` operator."
  end

  options = {name_in_index: nil}.merge(options) if graphql_only

  field_factory.call(
    name: name,
    type: type,
    graphql_only: graphql_only,
    parent_type: wrapping_type,
    **options
  ) do |field|
    yield field if block_given?

    unless indexing_only
      register_field(field.name, field, graphql_fields_by_name, "GraphQL", :indexing_only)
    end

    unless graphql_only
      register_field(field.name_in_index, field, indexing_fields_by_name_in_index, "indexing", :graphql_only) do |f|
        f.to_indexing_field_reference
      end
    end
  end
end

#nameString

Returns the name of this GraphQL type.

Returns:

  • (String)

    the name of this GraphQL type



110
111
112
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 110

def name
  type_ref.name
end

#paginated_collection_field(name, element_type, name_in_index: name, singular: nil) {|Field| ... } ⇒ void

Note:

Bear in mind that pagination does not have much efficiency benefit in this case: all elements of the collection will be retrieved when fetching this field from the datastore. The pagination implementation will just trim down the collection before returning it.

This method returns an undefined value.

An alternative to #field for when you have a list field that you want exposed as a paginated Relay connection rather than as a simple list.

Examples:

Define Author.books as a paginated collection field

ElasticGraph.define_schema do |schema|
  schema.object_type "Author" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.paginated_collection_field "books", "String"
    t.index "authors"
  end
end

Parameters:

  • name (String)

    name of the field

  • element_type (String)

    name of the type of element in the collection

  • name_in_index (String) (defaults to: name)

    the name of the field in the datastore index. Can be used to back a GraphQL field with a differently named field in the index.

  • singular (String) (defaults to: nil)

    indicates what the singular form of a field’s name is. When provided, ElasticGraph will define a groupedBy field (using the singular form) allowing clients to group by individual values from the field.

Yields:

  • (Field)

    the field for further customization

See Also:



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 300

def paginated_collection_field(name, element_type, name_in_index: name, singular: nil, &block)
  element_type_ref = schema_def_state.type_ref(element_type).to_final_form
  element_type = element_type_ref.name

  schema_def_state.paginated_collection_element_types << element_type

  backing_indexing_field = field(name, "[#{element_type}!]!", indexing_only: true, name_in_index: name_in_index, &block)

  field(
    name,
    element_type_ref.as_connection.name,
    name_in_index: name_in_index,
    type_for_derived_types: "[#{element_type}]",
    groupable: !!singular,
    sortable: false,
    graphql_only: true,
    singular: singular,
    backing_indexing_field: backing_indexing_field
  ) do |f|
    f.define_relay_pagination_arguments!
    block&.call(f)
  end
end

#relates_to_many(field_name, type, via:, dir:, singular:) {|Relationship| ... } ⇒ void

This method returns an undefined value.

Defines a “has many” relationship between the current indexed type and another indexed type by defining a pair of fields clients can use to navigate across indexed types in a single GraphQL query. The pair of generated fields will be Relay Connection types allowing you to filter, sort, paginate, and aggregated the related data.

Examples:

Use relates_to_many to define Team.players and Team.playerAggregations

ElasticGraph.define_schema do |schema|
  schema.object_type "Team" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.field "homeCity", "String"
    t.relates_to_many "players", "Player", via: "teamId", dir: :in, singular: "player"
    t.index "teams"
  end

  schema.object_type "Player" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.field "teamId", "ID"
    t.index "players"
  end
end

Parameters:

  • field_name (String)

    name of the relationship field

  • type (String)

    name of the related type

  • via (String)

    name of the foreign key field

  • dir (:in, :out)

    direction of the foreign key. Use :in for an inbound foreign key that resides on the related type and references the id of this type. Use :out for an outbound foreign key that resides on this type and references the id of the related type.

  • singular (String)

    singular form of the field_name; will be used (along with an Aggregations suffix) for the name of the generated aggregations field

Yields:

  • (Relationship)

    the generated relationship fields, for further customization

See Also:



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 397

def relates_to_many(field_name, type, via:, dir:, singular:)
  foreign_key_type = (dir == :out) ? "[ID!]!" : "ID"
  type_ref = schema_def_state.type_ref(type).to_final_form

  relates_to(field_name, type_ref.as_connection.name, via: via, dir: dir, foreign_key_type: foreign_key_type, cardinality: :many, related_type: type) do |f|
    f.argument schema_def_state.schema_elements.filter, type_ref.as_filter_input.name do |a|
      a.documentation "Used to filter the returned `#{field_name}` based on the provided criteria."
    end

    f.argument schema_def_state.schema_elements.order_by, "[#{type_ref.as_sort_order.name}!]" do |a|
      a.documentation "Used to specify how the returned `#{field_name}` should be sorted."
    end

    f.define_relay_pagination_arguments!

    yield f if block_given?
  end

  aggregations_name = schema_def_state.schema_elements.normalize_case("#{singular}_aggregations")
  relates_to(aggregations_name, type_ref.as_aggregation.as_connection.name, via: via, dir: dir, foreign_key_type: foreign_key_type, cardinality: :many, related_type: type) do |f|
    f.argument schema_def_state.schema_elements.filter, type_ref.as_filter_input.name do |a|
      a.documentation "Used to filter the `#{type}` documents that get aggregated over based on the provided criteria."
    end

    f.define_relay_pagination_arguments!

    yield f if block_given?

    f.documentation f.derived_documentation("Aggregations over the `#{field_name}` data")
  end
end

#relates_to_one(field_name, type, via:, dir:) {|Relationship| ... } ⇒ void

This method returns an undefined value.

Defines a “has one” relationship between the current indexed type and another indexed type by defining a field clients can use to navigate across indexed types in a single GraphQL query.

Examples:

Use relates_to_one to define Player.team

ElasticGraph.define_schema do |schema|
  schema.object_type "Team" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.field "homeCity", "String"
    t.index "teams"
  end

  schema.object_type "Player" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.relates_to_one "team", "Team", via: "teamId", dir: :out
    t.index "players"
  end
end

Parameters:

  • field_name (String)

    name of the relationship field

  • type (String)

    name of the related type

  • via (String)

    name of the foreign key field

  • dir (:in, :out)

    direction of the foreign key. Use :in for an inbound foreign key that resides on the related type and references the id of this type. Use :out for an outbound foreign key that resides on this type and references the id of the related type.

Yields:

  • (Relationship)

    the generated relationship fields, for further customization

See Also:



355
356
357
358
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 355

def relates_to_one(field_name, type, via:, dir:, &block)
  foreign_key_type = schema_def_state.type_ref(type).non_null? ? "ID!" : "ID"
  relates_to(field_name, type, via: via, dir: dir, foreign_key_type: foreign_key_type, cardinality: :one, related_type: type, &block)
end

#renamed_from(old_name) ⇒ void

Note:

In situations where this API applies, ElasticGraph will give you an error message indicating that you need to use this API or API#deleted_type. Likewise, when ElasticGraph no longer needs to know about this, it’ll give you a warning indicating the call to this method can be removed.

This method returns an undefined value.

Registers an old name that this type used to have in a prior version of the schema.

Examples:

Indicate that Widget used to be called Component.

ElasticGraph.define_schema do |schema|
  schema.object_type "Widget" do |t|
    t.renamed_from "Component"
  end
end

Parameters:

  • old_name (String)

    old name this field used to have in a prior version of the schema



262
263
264
265
266
267
268
269
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/type_with_subfields.rb', line 262

def renamed_from(old_name)
  schema_def_state.register_renamed_type(
    name,
    from: old_name,
    defined_at: caller_locations(2, 1).first, # : ::Thread::Backtrace::Location
    defined_via: %(type.renamed_from "#{old_name}")
  )
end