Skip to content

Commit

Permalink
Update schema definition guide
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Oct 28, 2024
1 parent 0d6f97b commit 5920bdf
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 159 deletions.
162 changes: 34 additions & 128 deletions guides/schema/definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Your GraphQL schema is a class that extends {{ "GraphQL::Schema" | api_doc }}, f
class MyAppSchema < GraphQL::Schema
max_complexity 400
query Types::Query
use GraphQL::Batch
use GraphQL::Dataloader

# Define hooks as class methods:
def self.resolve_type(type, obj, ctx)
Expand All @@ -34,165 +34,71 @@ class MyAppSchema < GraphQL::Schema
end
```

There are lots of schema configuration options:

- [root objects, introspection and orphan types](#root-objects-introspection-and-orphan-types)
- [object identification hooks](#object-identification-hooks)
- [execution configuration](#execution-configuration)
- [context class](#context-class)
- [default limits](#default-limits)
- [plugins](#plugins)
There are lots of schema configuration methods.

For defining GraphQL types, see the guides for those types: {% internal_link "object types", "/type_definitions/objects" %}, {% internal_link "interface types", "/type_definitions/interfaces" %}, {% internal_link "union types", "/type_definitions/unions" %}, {% internal_link "input object types", "/type_definitions/input_objects" %}, {% internal_link "enum types", "/type_definitions/enums" %}, and {% internal_link "scalar types", "/type_definitions/scalars" %}.

## Root Objects, Introspection and Orphan Types
## Types in the Schema

A GraphQL schema is a web of interconnected types, and it has a few starting points for discovering the elements of that web:
{{ "Schema.query" | api_doc }}, {{ "Schema.mutation" | api_doc }}, and {{ "Schema.subscription" | api_doc}} declare the [entry-point types](https://graphql.org/learn/schema/#the-query-and-mutation-types) of the schema.

__Root types__ (`query`, `mutation`, and `subscription`) are the [entry points for queries to the system](https://graphql.org/learn/schema/#the-query-and-mutation-types). Each one is an object type which can be connected to the schema by a method with the same name:
{{ "Schema.orphan_types" | api_doc }} declares object types which implement {% internal_link "Interfaces", "/type_definitions/interfaces" %} but aren't used as field return types in the schema. For more about this specific scenario, see {% internal_link "Orphan Types", "/type_definitions/interfaces#orphan-types" %}

```ruby
class MySchema < GraphQL::Schema
# Required:
query Types::Query
# Optional:
mutation Types::Mutation
subscription Types::Subscription
end
```
## Object Identification

__Introspection__ is a built-in part of the schema. Every schema has a default introspection system, but you can {% internal_link "customize it","/schema/introspection" %} and hook it up with `introspection`:

```ruby
class MySchema < GraphQL::Schema
introspection CustomIntrospection
end
```
Some GraphQL features use unique IDs to load objects:

__Orphan Types__ are types which should be in the schema, but can't be discovered by traversing the types and fields from `query`, `mutation` or `subscription`. This has one very specific use case, see {% internal_link "Orphan Types", "/type_definitions/interfaces#orphan-types" %}.
- the `node(id:)` field looks up objects by ID (See {% internal_link "Object Identification", "/schema/object_identification" %} for more about Relay-style object identification.)
- any arguments with `loads:` configurations look up objects by ID
- the {% internal_link "ObjectCache", "/object_cache/overview" %} uses IDs in its caching scheme

```ruby
class MySchema < GraphQL::Schema
orphan_types [Types::Comment, ...]
end
```
To use these features, you must provide some methods for generating UUIDs and fetching objects with them:

## Object Identification Hooks
{{ "Schema.object_from_id" | api_doc }} is called by GraphQL-Ruby to load objects directly from the database. It's usually used by the `node(id: ID!): Node` field (see {{ "GraphQL::Types::Relay::Node" | api_doc }}), Argument {% internal_link "loads:", "/mutations/mutation_classes#auto-loading-arguments" %}, or the {% internal_link "ObjectCache", "/object_cache/overview" %}. It receives a unique ID and must return the object for that ID, or `nil` if the object isn't found (or if it should be hidden from the current user).

A GraphQL schema needs a handful of hooks for finding and disambiguating objects while queries are executed.
{{ "Schema.id_from_object" | api_doc }} is used to implement `Node.id`. It should return a unique ID for the given object. This ID will later be sent to `object_from_id` to refetch the object.

__`resolve_type`__ is used when a specific object's corresponding GraphQL type must be determined. This happens for fields that return {% internal_link "interface", "/type_definitions/interfaces" %} or {% internal_link "union", "/type_definitions/unions" %} types. The class method `def self.resolve_type` is used:
Additionally, {{ "Schema.resolve_type" | api_doc }} is called by GraphQL-Ruby to get the runtime Object type for fields that return return {% internal_link "interface", "/type_definitions/interfaces" %} or {% internal_link "union", "/type_definitions/unions" %} types.

```ruby
class MySchema < GraphQL::Schema
def self.resolve_type(abstract_type, object, context)
# Disambiguate `object`, from among `abstract_type`'s members
# (`abstract_type` is an interface or union type.)
end
end
```
## Error Handling

`resolve_type` is also used by `loads:` to confirm that loaded objects match the configured type.
{{ "Schema.type_error" | api_doc }} handles type errors at runtime, read more in the {% internal_link "Type errors guide", "/errors/type_errors" %}.

__`object_from_id`__ is used by the `node(id: ID!): Node` field and `loads:` configuration. It receives a unique ID and must return the object for that ID, or `nil` if the object isn't found (or if it should be hidden from the current user).
__`id_from_object`__ is used to implement `Node.id`. It should return a unique ID for the given object. This ID will later be sent to `object_from_id` to refetch the object.
{{ "Schema.rescue_from" | api_doc }} defines error handlers for application errors. See the {% internal_link "error handling guide", "/errors/error_handling" %} for more.

See the {% internal_link "Object Identification guide", "/schema/object_identification" %} for more information about these methods.
{{ "Schema.parse_error" | api_doc }} and {{ "Schema.query_stack_error" | api_doc }} provide hooks for reporting errors to your bug tracker.

## Execution Configuration

__`trace_with`__ attaches tracer modules, see {% internal_link "Tracing", "/queries/tracing" %} for more.

```ruby
class MySchema < GraphQL::Schema
trace_with MetricTracer
end
```

__`query_analyzer`__ and __`multiplex_analyzer`__ accept processors for ahead-of-time query analysis, see {% internal_link "Analysis", "/queries/ast_analysis" %} for more.

```ruby
class MySchema < GraphQL::Schema
query_analyzer MyQueryAnalyzer
end
```

__`lazy_resolve`__ registers classes with {% internal_link "lazy execution", "/schema/lazy_execution" %}:
## Default Limits

```ruby
class MySchema < GraphQL::Schema
lazy_resolve Promise, :sync
end
```
{{ "Schema.max_depth" | api_doc }} and {{ "Schema.max_complexity" | api_doc }} apply some limits to incoming queries. See {% internal_link "Complexity and Depth", "/queries/complexity_and_depth" %} for more.

__`type_error`__ handles type errors at runtime, read more in the {% internal_link "Type errors guide", "/errors/type_errors" %}.
{{ "Schema.default_max_page_size" | api_doc }} applies limits to {% internal_link "connection fields", "/pagination/overview" %}.

```ruby
class MySchema < GraphQL::Schema
def self.type_error(type_err, context)
# Handle `type_err` in some way
end
end
```
{{ "Schema.validate_timeout" | api_doc }}, {{ "Schema.validate_max_errors" | api_doc }} and {{ "Schema.max_query_string_tokens" | api_doc }} all apply limits to query execution. See {% internal_link "Timeout", "/queries/timeout" %} for more.

__`rescue_from`__ accepts error handlers for application errors, for example:
## Introspection

```ruby
class MySchema < GraphQL::Schema
rescue_from(ActiveRecord::RecordNotFound) { "Not found" }
end
```
{{ "Schema.extra_types" | api_doc }} declares types which should be printed in the SDL and returned in introspection queries, but aren't otherwise used in the schema.

## Context Class
{{ "Schema.introspection" | api_doc }} can attach a {% internal_link "custom introspection system", "/schema/introspection" %} to the schema.

Usually, `context` is an instance of {{ "GraphQL::Query::Context" | api_doc }}, but you can create a custom subclass and attach it with `.context_class`, for example:
## Authorization

```ruby
class CustomContext < GraphQL::Query::Context
# Shorthand to get the current user
def viewer
self[:viewer]
end
end
{{ "Schema.unauthorized_object" | api_doc }} and {{ "Schema.unauthorized_field" | api_doc }} are called when {% internal_link "authorization hooks", "/authorization/authorization" %} return `false` during query execution.

class MySchema < GraphQL::Schema
context_class CustomContext
end
```
## Execution Configuration

Then, during execution, `context` will be an instance of `CustomContext`.
{{ "Schema.trace_with" | api_doc }} attaches tracer modules. See {% internal_link "Tracing", "/queries/tracing" %} for more.

## Default Limits
{{ "Schema.query_analyzer" | api_doc }} and {{ "Schema.multiplex_analyzer" }} accept processors for ahead-of-time query analysis, see {% internal_link "Analysis", "/queries/ast_analysis" %} for more.

`max_depth` and `max_complexity` apply some limits to incoming queries. See {% internal_link "Complexity and Depth", "/queries/complexity_and_depth" %} for more.
{{ "Schema.default_logger" | api_doc }} configures a logger for runtime. See {% internal_link "Logging", "/queries/logging" %}.

`default_max_page_size` applies limits to `Connection` fields.
{{ "Schema.context_class" | api_doc }} and {{ "Schema.query_class" | api_doc }} attach custom subclasses to your schema to use during execution.

```ruby
class MySchema < GraphQL::Schema
max_depth 15
max_complexity 300
default_max_page_size 20
end
```
{{ "Schema.lazy_resolve" | api_doc }} registers classes with {% internal_link "lazy execution", "/schema/lazy_execution" %}.

## Plugins

A plugin is an object that responds to `#use`. Plugins are used to attach new behavior to a schema without a lot of API overhead. For example, the gem's {% internal_link "monitoring tools", "/queries/tracing#monitoring" %} are plugins:

```ruby
class MySchema < GraphQL::Schema
use(GraphQL::Tracing::NewRelicTracing)
end
```

## Extra Types

Documentation-only types can be attached to the schema using {{ "Schema.extra_types" | api_doc }}. Types passed to this method will _always_ be available in introspection queries and SDL print-outs.

```ruby
class MySchema < GraphQL::Schema
# These aren't for queries, but will appear in documentation:
extra_types SystemErrorType, RateLimitExceptionType
end
```
{{ "Schema.use" | api_doc }} adds plugins to your schema. For example, {{ "GraphQL::Dataloader" | api_doc }} and {{ "GraphQL::Schema::Visibility" | api_doc }} are installed this way.
24 changes: 3 additions & 21 deletions guides/schema/object_identification.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,11 @@ desc: Working with unique global IDs
index: 8
---

Some GraphQL features use unique IDs to load objects:
GraphQL-Ruby ships with some helpers to implement [Relay-style object identification](https://relay.dev/graphql/objectidentification.htm).

- the `node(id:)` field looks up objects by ID
- any arguments with `loads:` configurations look up objects by ID
## Schema methods

To use these features, you must provide a function for generating UUIDs and fetching objects with them. In your schema, define `self.id_from_object` and `self.object_from_id`:

```ruby
class MySchema < GraphQL::Schema
def self.id_from_object(object, type_definition, query_ctx)
# Generate a unique string ID for `object` here
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
object.to_gid_param
end

def self.object_from_id(global_id, query_ctx)
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
GlobalID.find(global_id)
end
end
```

> [SQIDs](https://sqids.org/ruby) are an alternative to `to_gid_param` which generate shorter IDs. Here is a [detailed guide](https://blog.gripdev.xyz/2024/06/09/sqids-graphql-and-ruby/) of how they can be used.
See {% internal_link "the Schema definition guide", "/schema/definition#object-identification" %} for required top-level hooks.

## Node interface

Expand Down
Loading

0 comments on commit 5920bdf

Please sign in to comment.