Class: ElasticGraph::SchemaDefinition::Factory
- Inherits:
-
Object
- Object
- ElasticGraph::SchemaDefinition::Factory
- Defined in:
- lib/elastic_graph/schema_definition/factory.rb
Overview
A class responsible for instantiating all schema elements. We want all schema element instantiation to go through this one class to support extension libraries. ElasticGraph supports extension libraries that provide modules that get extended onto specific instances of ElasticGraph framework classes. We prefer this approach rather than having extension library modules applied via ‘include` or `prepend`, because they _permanently modify_ the host classes. ElasticGraph is designed to avoid all mutable global state, and that includes mutations to ElasticGraph class ancestor chains from extension libraries.
Concretely, if we included or prepended extension libraries modules, we’d have a hard time keeping our tests order-independent and deterministic while running all the ElasticGraph test suites in the same Ruby process. A test using an extension library could cause a core ElasticGraph class to get mutated in a way that impacts a test that runs in the same process later. Instead, we expect extension libraries to hook into ElasticGraph using ‘extend` on particular object instances.
But that creates a bit of a problem: how can an extension library extend a module onto every instance of a specific type of schema element while it is in use? The answer is this factory class:
- An extension library can extend a module onto `schema.factory`.
- That module can in turn override any of these factory methods and extend another module onto the schema
element instances.
Constant Summary collapse
- @@deprecated_element_new =
prevent_non_factory_instantiation_of(SchemaElements::DeprecatedElement)
- @@argument_new =
prevent_non_factory_instantiation_of(SchemaElements::Argument)
- @@built_in_types_new =
prevent_non_factory_instantiation_of(SchemaElements::BuiltInTypes)
- @@directive_new =
prevent_non_factory_instantiation_of(SchemaElements::Directive)
- @@enum_type_new =
prevent_non_factory_instantiation_of(SchemaElements::EnumType)
- @@enum_value_new =
prevent_non_factory_instantiation_of(SchemaElements::EnumValue)
- @@enums_for_indexed_types_new =
prevent_non_factory_instantiation_of(SchemaElements::EnumsForIndexedTypes)
- @@field_new =
prevent_non_factory_instantiation_of(SchemaElements::Field)
- @@graphql_sdl_enumerator_new =
prevent_non_factory_instantiation_of(SchemaElements::GraphQLSDLEnumerator)
- @@input_field_new =
prevent_non_factory_instantiation_of(SchemaElements::InputField)
- @@input_type_new =
prevent_non_factory_instantiation_of(SchemaElements::InputType)
- @@interface_type_new =
prevent_non_factory_instantiation_of(SchemaElements::InterfaceType)
- @@object_type_new =
prevent_non_factory_instantiation_of(SchemaElements::ObjectType)
- @@scalar_type_new =
prevent_non_factory_instantiation_of(SchemaElements::ScalarType)
- @@sort_order_enum_value_new =
prevent_non_factory_instantiation_of(SchemaElements::SortOrderEnumValue)
- @@type_reference_new =
prevent_non_factory_instantiation_of(SchemaElements::TypeReference)
- @@type_with_subfields_new =
prevent_non_factory_instantiation_of(SchemaElements::TypeWithSubfields)
- @@union_type_new =
prevent_non_factory_instantiation_of(SchemaElements::UnionType)
- @@field_source_new =
prevent_non_factory_instantiation_of(SchemaElements::FieldSource)
- @@relationship_new =
prevent_non_factory_instantiation_of(SchemaElements::Relationship)
Class Method Summary collapse
-
.prevent_non_factory_instantiation_of(klass) ⇒ Object
Helper method to help enforce our desired invariant: we want every instantiation of these schema element classes to happen via this factory method provided here.
Instance Method Summary collapse
- #build_relay_pagination_types(type_name, include_total_edge_count: false, derived_indexed_types: [], support_pagination: true, &customize_connection) ⇒ Object
-
#build_standard_filter_input_types_for_index_leaf_type(source_type, name_prefix: source_type, &define_filter_fields) ⇒ Object
Builds the standard set of filter input types for types which are indexing leaf types.
-
#build_standard_filter_input_types_for_index_object_type(source_type, name_prefix: source_type, &define_filter_fields) ⇒ Object
Builds the standard set of filter input types for types which are indexing object types.
-
#initialize(state) ⇒ Factory
constructor
A new instance of Factory.
-
#new_aggregated_values_type_for_index_leaf_type(index_leaf_type) ⇒ Object
Responsible for creating a new ‘*AggregatedValues` type for an index leaf type.
- #new_argument(field, name, value_type) ⇒ Object
- #new_built_in_types(api) ⇒ Object
- #new_deprecated_element(name, defined_at:, defined_via:) ⇒ Object
- #new_directive(name, arguments) ⇒ Object
- #new_enum_type(name, &block) ⇒ Object
- #new_enum_value(name, original_name) ⇒ Object
- #new_enums_for_indexed_types ⇒ Object
- #new_field_source(relationship_name:, field_path:) ⇒ Object
- #new_filter_input_type(source_type, name_prefix: source_type, category: :filter_input) ⇒ Object
- #new_graphql_sdl_enumerator(all_types) ⇒ Object
- #new_input_type(name) ⇒ Object
- #new_interface_type(name) ⇒ Object
- #new_object_type(name) ⇒ Object
- #new_relationship(field, cardinality:, related_type:, foreign_key:, direction:) ⇒ Object
- #new_scalar_type(name) ⇒ Object
- #new_sort_order_enum_value(enum_value, sort_order_field_path) ⇒ Object
- #new_type_reference(name) ⇒ Object
- #new_type_with_subfields(schema_kind, name, wrapping_type:, field_factory:) ⇒ Object
- #new_union_type(name) ⇒ Object
Constructor Details
#initialize(state) ⇒ Factory
Returns a new instance of Factory.
58 59 60 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 58 def initialize(state) @state = state end |
Class Method Details
.prevent_non_factory_instantiation_of(klass) ⇒ Object
Helper method to help enforce our desired invariant: we want every instantiation of these schema element classes to happen via this factory method provided here. To enforce that, this helper returns the ‘new` method (as a `Method` object) after removing it from the given class. That makes it impossible for `new` to be called by anyone except from the factory using the captured method object.
66 67 68 69 70 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 66 def self.prevent_non_factory_instantiation_of(klass) klass.method(:new).tap do klass.singleton_class.undef_method :new end end |
Instance Method Details
#build_relay_pagination_types(type_name, include_total_edge_count: false, derived_indexed_types: [], support_pagination: true, &customize_connection) ⇒ Object
210 211 212 213 214 215 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 210 def build_relay_pagination_types(type_name, include_total_edge_count: false, derived_indexed_types: [], support_pagination: true, &customize_connection) [ (edge_type_for(type_name) if support_pagination), connection_type_for(type_name, include_total_edge_count, derived_indexed_types, support_pagination, &customize_connection) ].compact end |
#build_standard_filter_input_types_for_index_leaf_type(source_type, name_prefix: source_type, &define_filter_fields) ⇒ Object
Builds the standard set of filter input types for types which are indexing leaf types.
All GraphQL leaf types (enums and scalars) are indexing leaf types, but some GraphQL object types are as well. For example, ‘GeoLocation` is an object type in GraphQL (with separate lat/long fields) but is an indexing leaf type because we use the datastore `geo_point` type for it.
189 190 191 192 193 194 195 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 189 def build_standard_filter_input_types_for_index_leaf_type(source_type, name_prefix: source_type, &define_filter_fields) single_value_filter = new_filter_input_type(source_type, name_prefix: name_prefix, &define_filter_fields) list_filter = new_list_filter_input_type(source_type, name_prefix: name_prefix, any_satisfy_type_category: :list_element_filter_input) list_element_filter = new_list_element_filter_input_type(source_type, name_prefix: name_prefix, &define_filter_fields) [single_value_filter, list_filter, list_element_filter] end |
#build_standard_filter_input_types_for_index_object_type(source_type, name_prefix: source_type, &define_filter_fields) ⇒ Object
Builds the standard set of filter input types for types which are indexing object types.
Most GraphQL object types are indexing object types as well, but not all. For example, ‘GeoLocation` is an object type in GraphQL (with separate lat/long fields) but is an indexing leaf type because we use the datastore `geo_point` type for it.
202 203 204 205 206 207 208 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 202 def build_standard_filter_input_types_for_index_object_type(source_type, name_prefix: source_type, &define_filter_fields) single_value_filter = new_filter_input_type(source_type, name_prefix: name_prefix, &define_filter_fields) list_filter = new_list_filter_input_type(source_type, name_prefix: name_prefix, any_satisfy_type_category: :filter_input) fields_list_filter = new_fields_list_filter_input_type(source_type, name_prefix: name_prefix) [single_value_filter, list_filter, fields_list_filter] end |
#new_aggregated_values_type_for_index_leaf_type(index_leaf_type) ⇒ Object
Responsible for creating a new ‘*AggregatedValues` type for an index leaf type.
An index leaf type is a scalar, enum, object type that is backed by a single, indivisible field in the index. All scalar and enum types are index leaf types, and object types rarely (but sometimes) are. For example, the ‘GeoLocation` object type has two subfields (`latitude` and `longitude`) but is backed by a single `geo_point` field in the index, so it is an index leaf type.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 285 def new_aggregated_values_type_for_index_leaf_type(index_leaf_type) new_object_type @state.type_ref(index_leaf_type).as_aggregated_values.name do |type| type.graphql_only true type.documentation "A return type used from aggregations to provided aggregated values over `#{index_leaf_type}` fields." type.resolve_fields_with :object_with_lookahead type.(elasticgraph_category: :scalar_aggregated_values) type.field @state.schema_elements.approximate_distinct_value_count, "JsonSafeLong", graphql_only: true do |f| # Note: the 1-6% accuracy figure comes from the Elasticsearch docs: # https://www.elastic.co/guide/en/elasticsearch/reference/8.10/search-aggregations-metrics-cardinality-aggregation.html#_counts_are_approximate f.documentation <<~EOS An approximation of the number of unique values for this field within this grouping. The approximation uses the HyperLogLog++ algorithm from the [HyperLogLog in Practice](https://research.google.com/pubs/archive/40671.pdf) paper. The accuracy of the returned value varies based on the specific dataset, but it usually differs from the true distinct value count by less than 7%. EOS f. empty_bucket_value: 0, function: :cardinality end yield type end end |
#new_argument(field, name, value_type) ⇒ Object
77 78 79 80 81 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 77 def new_argument(field, name, value_type) @@argument_new.call(@state, field, name, value_type).tap do |argument| yield argument if block_given? end end |
#new_built_in_types(api) ⇒ Object
84 85 86 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 84 def new_built_in_types(api) @@built_in_types_new.call(api, @state) end |
#new_deprecated_element(name, defined_at:, defined_via:) ⇒ Object
72 73 74 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 72 def new_deprecated_element(name, defined_at:, defined_via:) @@deprecated_element_new.call(schema_def_state: @state, name: name, defined_at: defined_at, defined_via: defined_via) end |
#new_directive(name, arguments) ⇒ Object
89 90 91 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 89 def new_directive(name, arguments) @@directive_new.call(name, arguments) end |
#new_enum_type(name, &block) ⇒ Object
94 95 96 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 94 def new_enum_type(name, &block) @@enum_type_new.call(@state, name, &(_ = block)) end |
#new_enum_value(name, original_name) ⇒ Object
99 100 101 102 103 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 99 def new_enum_value(name, original_name) @@enum_value_new.call(@state, name, original_name) do |enum_value| yield enum_value if block_given? end end |
#new_enums_for_indexed_types ⇒ Object
106 107 108 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 106 def new_enums_for_indexed_types @@enums_for_indexed_types_new.call(@state) end |
#new_field_source(relationship_name:, field_path:) ⇒ Object
262 263 264 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 262 def new_field_source(relationship_name:, field_path:) @@field_source_new.call(relationship_name, field_path) end |
#new_filter_input_type(source_type, name_prefix: source_type, category: :filter_input) ⇒ Object
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 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 139 def new_filter_input_type(source_type, name_prefix: source_type, category: :filter_input) all_of = @state.schema_elements.all_of any_of = @state.schema_elements.any_of new_input_type(@state.type_ref(name_prefix).as_static_derived_type(category).name) do |t| t.documentation <<~EOS Input type used to specify filters on `#{source_type}` fields. Will match all documents if passed as an empty object (or as `null`). EOS t.field @state.schema_elements.any_of, "[#{t.name}!]" do |f| f.documentation <<~EOS Matches records where any of the provided sub-filters evaluate to true. This works just like an OR operator in SQL. When `null` is passed, matches all documents. When an empty list is passed, this part of the filter matches no documents. EOS end t.field @state.schema_elements.all_of, "[#{t.name}!]" do |f| f.documentation <<~EOS Matches records where all of the provided sub-filters evaluate to true. This works just like an AND operator in SQL. Note: multiple filters are automatically ANDed together. This is only needed when you have multiple filters that can't be provided on a single `#{t.name}` input because of collisions between key names. For example, if you want to AND multiple OR'd sub-filters (the equivalent of (A OR B) AND (C OR D)), you could do #{all_of}: [{#{any_of}: [...]}, {#{any_of}: [...]}]. When `null` or an empty list is passed, matches all documents. EOS end t.field @state.schema_elements.not, t.name do |f| f.documentation <<~EOS Matches records where the provided sub-filter evaluates to false. This works just like a NOT operator in SQL. When `null` or an empty object is passed, matches no documents. EOS end yield t end end |
#new_graphql_sdl_enumerator(all_types) ⇒ Object
118 119 120 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 118 def new_graphql_sdl_enumerator(all_types) @@graphql_sdl_enumerator_new.call(@state, all_types) end |
#new_input_type(name) ⇒ Object
132 133 134 135 136 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 132 def new_input_type(name) @@input_type_new.call(@state, name) do |input_type| yield input_type end end |
#new_interface_type(name) ⇒ Object
217 218 219 220 221 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 217 def new_interface_type(name) @@interface_type_new.call(@state, name.to_s) do |interface_type| yield interface_type end end |
#new_object_type(name) ⇒ Object
224 225 226 227 228 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 224 def new_object_type(name) @@object_type_new.call(@state, name.to_s) do |object_type| yield object_type if block_given? end end |
#new_relationship(field, cardinality:, related_type:, foreign_key:, direction:) ⇒ Object
267 268 269 270 271 272 273 274 275 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 267 def new_relationship(field, cardinality:, related_type:, foreign_key:, direction:) @@relationship_new.call( field, cardinality: cardinality, related_type: , foreign_key: foreign_key, direction: direction ) end |
#new_scalar_type(name) ⇒ Object
231 232 233 234 235 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 231 def new_scalar_type(name) @@scalar_type_new.call(@state, name.to_s) do |scalar_type| yield scalar_type end end |
#new_sort_order_enum_value(enum_value, sort_order_field_path) ⇒ Object
238 239 240 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 238 def new_sort_order_enum_value(enum_value, sort_order_field_path) @@sort_order_enum_value_new.call(enum_value, sort_order_field_path) end |
#new_type_reference(name) ⇒ Object
243 244 245 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 243 def new_type_reference(name) @@type_reference_new.call(name, @state) end |
#new_type_with_subfields(schema_kind, name, wrapping_type:, field_factory:) ⇒ Object
248 249 250 251 252 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 248 def new_type_with_subfields(schema_kind, name, wrapping_type:, field_factory:) @@type_with_subfields_new.call(schema_kind, @state, name, wrapping_type: wrapping_type, field_factory: field_factory) do |type_with_subfields| yield type_with_subfields end end |
#new_union_type(name) ⇒ Object
255 256 257 258 259 |
# File 'lib/elastic_graph/schema_definition/factory.rb', line 255 def new_union_type(name) @@union_type_new.call(@state, name.to_s) do |union_type| yield union_type end end |