File: schema_setup.md

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (86 lines) | stat: -rw-r--r-- 3,904 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
---
layout: guide
doc_stub: false
search: true
enterprise: true
section: GraphQL Enterprise - Object Cache
title: Schema Setup
desc: Prepare your schema to serve cached responses
index: 1
---

To prepare the schema to serve cached responses, you have to add `GraphQL::Enterprise::ObjectCache` and implement a few hooks.

## Add the Cache

In your schema, add `use GraphQL::Enterprise::ObjectCache, redis: ...`:

```ruby
class MySchema < GraphQL::Schema
  use GraphQL::Enterprise::ObjectCache, redis: CACHE_REDIS
end
```

See the {% internal_link "Redis guide", "/object_cache/redis" %} for details about configuring cache storage.

Additionally, it accepts some options for customizing how introspection is cached, see {% internal_link "Caching Introspection", "/object_cache/caching#caching-introspection" %}

## Context Fingerprint

Additionally, you should implement `def self.private_context_fingerprint_for(context)` to return a string identifying the private scope of the given context. This method will be called whenever a query includes a {% internal_link "`public: false` type or field", "/object_cache/caching#public" %}. For example:

```ruby
class MySchema < GraphQL::Schema
  # ...
  def self.private_context_fingerprint_for(context)
    viewer = context[:viewer]
    if viewer.nil?
      # This should never happen, but just in case:
      raise("Invariant: No viewer in context! Can't create a private context fingerprint" )
    end

    # include permissions in the fingerprint so that if the viewer's permissions change, the cache will be invalidated
    permission_fingerprint = viewer.team_memberships.map { |tm| "#{tm.team_id}/#{tm.permission}" }.join(":")

    "user:#{viewer.id}:#{permission_fingerprint}"
  end
end
```

Whenever queries including `public: false` are cached, the private context fingerprint will be part of the cache key, preventing responses from being shared between different viewers.

The returned String should reflect any aspects of `context` that, if changed, should invalidate the cache. For example, if a user's permission level or team memberships change, then any previously-cached responses should be ignored.

## Object Fingerprint

In order to determine whether cached results should be returned or invalidated, GraphQL needs a way to determine the "version" of each object in the query. It uses `Schema.object_fingerprint_for(object)` to do this. By default, it checks `.cache_key_with_version` (implemented by Rails), then `.to_param`, then it returns `nil`. Returning `nil` tells the cache not to use the cache _at all_. To customize this behavior, you can implement `def self.object_fingerprint_for(object)` in your schema:

```ruby
class MySchema < GraphQL::Schema
  # ...

  # For example, if you defined `.custom_cache_key` and `.uncacheable?`
  # on objects in your application:
  def self.object_fingerprint_for(object)
    if object.respond_to?(:custom_cache_key)
      object.custom_cache_key
    elsif object.respond_to?(:uncacheable?) && object.uncacheable?
      nil # don't cache queries containing this object
    else
      super
    end
  end
end
```

The returned strings are used as cache keys in the database -- whenever they change, stale data is left to be {% internal_link "cleaned up by Redis", "/object_cache/redis#memory-management" %}.

## Object Identification

`ObjectCache` depends on object identification hooks used elsewhere in GraphQL-Ruby:

- `def self.id_from_object(object, type, context)` which returns a globally-unique String id for `object`
- `def self.object_from_id(id, context)` which returns the application object for the given globally-unique `id`
- `def self.resolve_type(abstract_type, object, context)` which returns a GraphQL object type definition to use for `object`

After your schema is setup, you can {% internal_link "configure caching on your types and fields", "/object_cache/caching", %}.