Class: ElasticGraph::SchemaDefinition::SchemaElements::ScalarType
- Inherits:
-
Struct
- Object
- Struct
- ElasticGraph::SchemaDefinition::SchemaElements::ScalarType
- 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/scalar_type.rb
Overview
Defines a GraphQL scalar type. ElasticGraph itself uses this to define a few
common scalar types (e.g. Date
and DateTime
), but it is also available to you to use to define your own custom scalar types.
Constant Summary
Constants included from Mixins::HasTypeInfo
Mixins::HasTypeInfo::CUSTOMIZABLE_DATASTORE_PARAMS
Instance Attribute Summary collapse
-
#schema_def_state ⇒ State
readonly
Schema definition state.
Attributes included from Mixins::HasDocumentation
Instance Method Summary collapse
-
#coerce_with(adapter_name, defined_at:) ⇒ void
Specifies the scalar coercion adapter that should be used for this scalar type.
-
#mapping(**options) ⇒ void
Defines the Elasticsearch/OpenSearch field mapping type and mapping parameters for a field or type.
-
#name ⇒ String
Name of the scalar type.
-
#prepare_for_indexing_with(preparer_name, defined_at:) ⇒ void
Specifies an indexing preparer that should be used for this scalar type.
-
#to_sdl ⇒ String
The GraphQL SDL form of this scalar.
Methods included from Mixins::HasTypeInfo
#json_schema, #json_schema_options, #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
Methods included from Mixins::VerifiesGraphQLName
Instance Attribute Details
#schema_def_state ⇒ State (readonly)
Returns schema definition state.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 42 class ScalarType < Struct.new(:schema_def_state, :type_ref, :mapping_type, :runtime_metadata, :aggregated_values_customizations) # `Struct.new` provides the following methods: # @dynamic type_ref, runtime_metadata prepend Mixins::VerifiesGraphQLName include Mixins::CanBeGraphQLOnly include Mixins::HasDocumentation include Mixins::HasDirectives include Mixins::HasDerivedGraphQLTypeCustomizations include Mixins::HasReadableToSAndInspect.new { |t| t.name } # `HasTypeInfo` provides the following methods: # @dynamic mapping_options, json_schema_options include Mixins::HasTypeInfo # @dynamic graphql_only? # @private def initialize(schema_def_state, name) super(schema_def_state, schema_def_state.type_ref(name).to_final_form) # Default the runtime metadata before yielding, so it can be overridden as needed. self. = SchemaArtifacts::RuntimeMetadata::ScalarType.new( coercion_adapter_ref: SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_COERCION_ADAPTER_REF, indexing_preparer_ref: SchemaArtifacts::RuntimeMetadata::ScalarType::DEFAULT_INDEXING_PREPARER_REF ) yield self missing = [ ("`mapping`" if .empty?), ("`json_schema`" if .empty?) ].compact if missing.any? raise Errors::SchemaError, "Scalar types require `mapping` and `json_schema` to be configured, but `#{name}` lacks #{missing.join(" and ")}." end end # @return [String] name of the scalar type def name type_ref.name end # (see Mixins::HasTypeInfo#mapping) def mapping(**) self.mapping_type = .fetch(:type) do raise Errors::SchemaError, "Must specify a mapping `type:` on custom scalars but was missing on the `#{name}` type." end super end # Specifies the scalar coercion adapter that should be used for this scalar type. The scalar coercion adapter is responsible # for validating and coercing scalar input values, and converting scalar return values to a form suitable for JSON serialization. # # @note For examples of scalar coercion adapters, see `ElasticGraph::GraphQL::ScalarCoercionAdapters`. # @note If the `defined_at` require path requires any directories be put on the Ruby `$LOAD_PATH`, you are responsible for doing # that before booting {ElasticGraph::GraphQL}. # # @param adapter_name [String] fully qualified Ruby class name of the adapter # @param defined_at [String] the `require` path of the adapter # @return [void] # # @example Register a coercion adapter # ElasticGraph.define_schema do |schema| # schema.scalar_type "PhoneNumber" do |t| # t.mapping type: "keyword" # t.json_schema type: "string", pattern: "^\\+[1-9][0-9]{1,14}$" # t.coerce_with "CoercionAdapters::PhoneNumber", defined_at: "./coercion_adapters/phone_number" # end # end def coerce_with(adapter_name, defined_at:) self. = .with(coercion_adapter_ref: { "extension_name" => adapter_name, "require_path" => defined_at }).tap(&:load_coercion_adapter) # verify the adapter is valid. end # Specifies an indexing preparer that should be used for this scalar type. The indexing preparer is responsible for preparing # scalar values before indexing them, performing any desired formatting or normalization. # # @note For examples of scalar coercion adapters, see `ElasticGraph::Indexer::IndexingPreparers`. # @note If the `defined_at` require path requires any directories be put on the Ruby `$LOAD_PATH`, you are responsible for doing # that before booting {ElasticGraph::GraphQL}. # # @param preparer_name [String] fully qualified Ruby class name of the indexing preparer # @param defined_at [String] the `require` path of the preparer # @return [void] # # @example Register an indexing preparer # ElasticGraph.define_schema do |schema| # schema.scalar_type "PhoneNumber" do |t| # t.mapping type: "keyword" # t.json_schema type: "string", pattern: "^\\+[1-9][0-9]{1,14}$" # # t.prepare_for_indexing_with "IndexingPreparers::PhoneNumber", # defined_at: "./indexing_preparers/phone_number" # end # end def prepare_for_indexing_with(preparer_name, defined_at:) self. = .with(indexing_preparer_ref: { "extension_name" => preparer_name, "require_path" => defined_at }).tap(&:load_indexing_preparer) # verify the preparer is valid. end # @return [String] the GraphQL SDL form of this scalar def to_sdl "#{formatted_documentation}scalar #{name} #{directives_sdl}" end # Registers a block which will be used to customize the derived `*AggregatedValues` object type. # # @private def customize_aggregated_values_type(&block) self.aggregated_values_customizations = block end # @private def aggregated_values_type if aggregated_values_customizations type_ref.as_aggregated_values else schema_def_state.type_ref("NonNumeric").as_aggregated_values end end # @private def to_indexing_field_type Indexing::FieldType::Scalar.new(scalar_type: self) end # @private def derived_graphql_types return [] if graphql_only? pagination_types = if schema_def_state.paginated_collection_element_types.include?(name) schema_def_state.factory.build_relay_pagination_types(name, include_total_edge_count: true) else [] # : ::Array[ObjectType] end (to_input_filters + pagination_types).tap do |derived_types| if (aggregated_values_type = to_aggregated_values_type) derived_types << aggregated_values_type end end end # @private def indexed? false end private EQUAL_TO_ANY_OF_DOC = <<~EOS Matches records where the field value is equal to any of the provided values. This works just like an IN operator in SQL. Will be ignored when `null` is passed. When an empty list is passed, will cause this part of the filter to match no documents. When `null` is passed in the list, will match records where the field value is `null`. EOS GT_DOC = <<~EOS Matches records where the field value is greater than (>) the provided value. Will be ignored when `null` is passed. EOS GTE_DOC = <<~EOS Matches records where the field value is greater than or equal to (>=) the provided value. Will be ignored when `null` is passed. EOS LT_DOC = <<~EOS Matches records where the field value is less than (<) the provided value. Will be ignored when `null` is passed. EOS LTE_DOC = <<~EOS Matches records where the field value is less than or equal to (<=) the provided value. Will be ignored when `null` is passed. EOS def to_input_filters # Note: all fields on inputs should be nullable, to support parameterized queries where # the parameters are allowed to be set to `null`. We also now support nulls within lists. # For floats, we may want to remove the `equal_to_any_of` operator at some point. # In many languages. checking exact equality with floats is problematic. # For example, in IRB: # # 2.7.1 :003 > 0.3 == (0.1 + 0.2) # => false # # However, it's not yet clear if that issue will come up with GraphQL, because # float values are serialized on the wire as JSON, using an exact decimal # string representation. So for now we are keeping `equal_to_any_of`. schema_def_state.factory.build_standard_filter_input_types_for_index_leaf_type(name) do |t| # Normally, we use a nullable type for `equal_to_any_of`, to allow a filter expression like this: # # filter: {optional_field: {equal_to_any_of: [null]}} # # That filter expression matches documents where `optional_field == null`. However, # we cannot support this: # # filter: {tags: {any_satisfy: {equal_to_any_of: [null]}}} # # We can't support that because we implement filtering on `null` using an `exists` query: # https://www.elastic.co/guide/en/elasticsearch/reference/8.10/query-dsl-exists-query.html # # ...but that works based on the field existing (or not), and does not let us filter on the # presence or absence of `null` within a list. # # So, here we make the field non-null if we're in an `any_satisfy` context (as indicated by # the type ending with `ListElementFilterInput`). equal_to_any_of_type = t.type_ref.list_element_filter_input? ? "[#{name}!]" : "[#{name}]" t.field schema_def_state.schema_elements.equal_to_any_of, equal_to_any_of_type do |f| f.documentation EQUAL_TO_ANY_OF_DOC end if mapping_type_efficiently_comparable? t.field schema_def_state.schema_elements.gt, name do |f| f.documentation GT_DOC end t.field schema_def_state.schema_elements.gte, name do |f| f.documentation GTE_DOC end t.field schema_def_state.schema_elements.lt, name do |f| f.documentation LT_DOC end t.field schema_def_state.schema_elements.lte, name do |f| f.documentation LTE_DOC end end end end def to_aggregated_values_type return nil unless (customization_block = aggregated_values_customizations) schema_def_state.factory.new_aggregated_values_type_for_index_leaf_type(name, &customization_block) end # https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html # https://www.elastic.co/guide/en/elasticsearch/reference/7.13/number.html#number NUMERIC_TYPES = %w[long integer short byte double float half_float scaled_float unsigned_long].to_set DATE_TYPES = %w[date date_nanos].to_set # The Elasticsearch/OpenSearch docs do not exhaustively give a list of types on which range queries are efficient, # but the docs are clear that it is efficient on numeric and date types, and is inefficient on string # types: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html COMPARABLE_TYPES = NUMERIC_TYPES | DATE_TYPES def mapping_type_efficiently_comparable? COMPARABLE_TYPES.include?(mapping_type) end end |
Instance Method Details
#coerce_with(adapter_name, defined_at:) ⇒ void
For examples of scalar coercion adapters, see ElasticGraph::GraphQL::ScalarCoercionAdapters
.
If the defined_at
require path requires any directories be put on the Ruby $LOAD_PATH
, you are responsible for doing
that before booting GraphQL.
This method returns an undefined value.
Specifies the scalar coercion adapter that should be used for this scalar type. The scalar coercion adapter is responsible for validating and coercing scalar input values, and converting scalar return values to a form suitable for JSON serialization.
113 114 115 116 117 118 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 113 def coerce_with(adapter_name, defined_at:) self. = .with(coercion_adapter_ref: { "extension_name" => adapter_name, "require_path" => defined_at }).tap(&:load_coercion_adapter) # verify the adapter is valid. end |
#mapping(**options) ⇒ void
This method returns an undefined value.
Defines the Elasticsearch/OpenSearch field mapping type
and mapping parameters for a field or type.
The options passed here will be included in the generated datastore_config.yaml
artifact that ElasticGraph uses to configure
Elasticsearch/OpenSearch.
Can be called multiple times; each time, the options will be merged into the existing options.
This is required on a ElasticGraph::SchemaDefinition::SchemaElements::ScalarType; without it, ElasticGraph would have no way to know how the datatype should be indexed in the datastore.
On a Field, this can be used to customize how a field is indexed. For example, String
fields are normally
indexed as keywords; to instead index a String
field for full text search, you’d need to configure mapping type: "text"
.
On a ObjectType, this can be used to use a specific Elasticsearch/OpenSearch data type for something that is
modeled as an object in GraphQL. For example, we use it for the GeoLocation
type so they get indexed in Elasticsearch using the
geo_point type.
86 87 88 89 90 91 92 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 86 def mapping(**) self.mapping_type = .fetch(:type) do raise Errors::SchemaError, "Must specify a mapping `type:` on custom scalars but was missing on the `#{name}` type." end super end |
#name ⇒ String
Returns name of the scalar type.
81 82 83 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 81 def name type_ref.name end |
#prepare_for_indexing_with(preparer_name, defined_at:) ⇒ void
For examples of scalar coercion adapters, see ElasticGraph::Indexer::IndexingPreparers
.
If the defined_at
require path requires any directories be put on the Ruby $LOAD_PATH
, you are responsible for doing
that before booting GraphQL.
This method returns an undefined value.
Specifies an indexing preparer that should be used for this scalar type. The indexing preparer is responsible for preparing scalar values before indexing them, performing any desired formatting or normalization.
141 142 143 144 145 146 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 141 def prepare_for_indexing_with(preparer_name, defined_at:) self. = .with(indexing_preparer_ref: { "extension_name" => preparer_name, "require_path" => defined_at }).tap(&:load_indexing_preparer) # verify the preparer is valid. end |
#to_sdl ⇒ String
Returns the GraphQL SDL form of this scalar.
149 150 151 |
# File 'elasticgraph-schema_definition/lib/elastic_graph/schema_definition/schema_elements/scalar_type.rb', line 149 def to_sdl "#{formatted_documentation}scalar #{name} #{directives_sdl}" end |