Class: Parse::GraphQL::TypeGenerator
- Inherits:
-
Object
- Object
- Parse::GraphQL::TypeGenerator
- Defined in:
- lib/parse/graphql/type_generator.rb
Overview
Generates ‘GraphQL::Schema::Object` subclasses from Parse::Object subclasses by reading the class-level property and association registries: `fields`, `field_map`, `references` (belongs_to), `has_one_associations`, `has_many_associations`, and `relations`.
v1 scope: type shape only. Default graphql-ruby field resolution invokes the same-named method on the underlying Ruby object, and Parse::Object subclasses already expose typed accessors (‘song.album`, `band.fans`, etc.) — so no resolvers are emitted. Pagination arguments, Loaders, and Relay connections are deferred until query/mutation passthrough lands (TODO §7).
Cross-class references (pointers, has_many) require all referenced model types to be in the registry BEFORE field emission. Use ‘generate_all` to codegen a list of models in one call (two-pass: stub classes first, then add fields), or `generate` with an explicit `registry:` hash if you want incremental control.
Constant Summary collapse
- SCALAR_TYPE_MAP =
Maps Parse property ‘:type` symbols to graphql-ruby built-ins. Cross-class references (`:pointer`, `:relation`) are NOT in this map — they need the target class and are handled per-field. Nil mappings fall through to the JSON scalar with a warning.
{ string: ::GraphQL::Types::String, integer: ::GraphQL::Types::Int, float: ::GraphQL::Types::Float, boolean: ::GraphQL::Types::Boolean, date: ::GraphQL::Types::ISO8601DateTime, timezone: ::GraphQL::Types::String, phone: ::GraphQL::Types::String, email: ::GraphQL::Types::String, # Parse Bytes is a `{__type: Bytes, base64: ...}` wrapper, not a # bare string — the accessor returns the wrapped object, so # falling through to the JSON scalar (with a warn) is safer than # ::String.to_s on the hash. Subscribers needing structured byte # access should declare a `:string` property containing the base64. bytes: nil, polygon: nil, # JSON fallback (GeoJSON-shaped, multi-ring) # Parse vector columns are bounded-length Float arrays # (embeddings). Emit as a typed list of Floats, not JSON. vector: [::GraphQL::Types::Float], array: nil, # JSON fallback (no element type known) object: nil, # JSON fallback }.freeze
- OMITTED_TYPES =
Property types that are deliberately omitted. ACL is internal authz metadata; exposing it via GraphQL would leak authorization shape into clients. The objectId is exposed under the canonical ‘id: ID` field separately.
%i[acl].freeze
Class Method Summary collapse
- .build_stub(model_class) ⇒ Object
-
.detect_name_collisions!(registry) ⇒ Object
graphql-ruby requires unique ‘graphql_name` across the schema.
-
.generate(model_class, registry: nil) ⇒ Class
Generate a single type.
-
.generate_all(model_classes) ⇒ Hash{String => Class}
Generate types for a list of models in one call.
- .validate!(model_class) ⇒ Object
Instance Method Summary collapse
-
#initialize(model_class, registry:, _prebuilt:) ⇒ TypeGenerator
constructor
A new instance of TypeGenerator.
- #populate_fields ⇒ Object
Constructor Details
#initialize(model_class, registry:, _prebuilt:) ⇒ TypeGenerator
Returns a new instance of TypeGenerator.
128 129 130 131 132 |
# File 'lib/parse/graphql/type_generator.rb', line 128 def initialize(model_class, registry:, _prebuilt:) @model_class = model_class @registry = registry @klass = _prebuilt end |
Class Method Details
.build_stub(model_class) ⇒ Object
119 120 121 122 123 124 125 126 |
# File 'lib/parse/graphql/type_generator.rb', line 119 def self.build_stub(model_class) type_name = model_class.parse_class.tr("_", "") description_text = "Generated GraphQL type for Parse class #{model_class.parse_class}." Class.new(::GraphQL::Schema::Object) do graphql_name type_name description description_text end end |
.detect_name_collisions!(registry) ⇒ Object
graphql-ruby requires unique ‘graphql_name` across the schema. `build_stub` strips underscores so `_User` and `User` collapse to the same name. Raise a clear error rather than letting graphql-ruby’s ‘DuplicateNamesError` surface at schema-build time, which doesn’t say which Parse classes collided.
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/parse/graphql/type_generator.rb', line 84 def self.detect_name_collisions!(registry) by_gql_name = registry.each_with_object({}) do |(parse_name, type), acc| (acc[type.graphql_name] ||= []) << parse_name end collisions = by_gql_name.select { |_, names| names.size > 1 } return if collisions.empty? details = collisions.map { |gql, parse| "#{gql} ← #{parse.join(', ')}" }.join('; ') raise "Parse::GraphQL::TypeGenerator: graphql_name collisions: #{details}. " \ "Parse class names that differ only by underscores collapse to the same " \ "GraphQL type name. Rename or generate the conflicting classes separately." end |
.generate(model_class, registry: nil) ⇒ Class
Generate a single type. If the model has belongs_to / has_many references to other Parse classes, those targets must already be in the registry — otherwise an error is raised at field-emit time. Prefer ‘generate_all` when you have a graph of models.
104 105 106 107 108 109 110 |
# File 'lib/parse/graphql/type_generator.rb', line 104 def self.generate(model_class, registry: nil) validate!(model_class) registry ||= {} stub = registry[model_class.parse_class] ||= build_stub(model_class) new(model_class, registry: registry, _prebuilt: stub).populate_fields stub end |
.generate_all(model_classes) ⇒ Hash{String => Class}
Generate types for a list of models in one call. Two-pass: creates empty stub classes for every model first so cross-class references resolve regardless of declaration order.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/parse/graphql/type_generator.rb', line 64 def self.generate_all(model_classes) registry = {} # Pass 1: stub classes registered by parse_class name. model_classes.each do |model| validate!(model) registry[model.parse_class] = build_stub(model) end # Pass 2: populate fields. Cross-references now resolve. model_classes.each do |model| new(model, registry: registry, _prebuilt: registry[model.parse_class]).populate_fields end detect_name_collisions!(registry) registry end |
.validate!(model_class) ⇒ Object
112 113 114 115 116 117 |
# File 'lib/parse/graphql/type_generator.rb', line 112 def self.validate!(model_class) unless model_class.is_a?(Class) && model_class < Parse::Object raise ArgumentError, "Parse::GraphQL::TypeGenerator requires a Parse::Object subclass, got #{model_class.inspect}" end end |
Instance Method Details
#populate_fields ⇒ Object
134 135 136 137 138 139 140 |
# File 'lib/parse/graphql/type_generator.rb', line 134 def populate_fields emit_scalar_fields emit_belongs_to_fields emit_has_one_fields emit_has_many_fields @klass end |