Skip to content

Commit af2a7df

Browse files
committed
enterprise-1.5.6
1 parent c5ae389 commit af2a7df

File tree

3 files changed

+75
-1
lines changed

3 files changed

+75
-1
lines changed

CHANGELOG-enterprise.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
### Bug Fix
1010

11+
# 1.5.6 (13 Dec 2024)
12+
13+
- ObjectCache: Add `CacheableRelation` helper for top-level ActiveRecord relations
14+
1115
# 1.5.5 (10 Dec 2024)
1216

1317
- Changesets: Add missing `ensure_loaded` call for class-based changesets

guides/object_cache/caching.md

+70-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ Under the hood, `ttl:` is implemented with Redis's `EXPIRE`.
7373

7474
## Caching lists and connections
7575

76-
Lists and connections require a little extra consideration. In order to effectively bust the cache, items that belong to the list of "parent" object should __update the parent__ (eg, Rails `.touch`) whenever they're created, destroyed, or updated. For example, if there's a list of players on a team:
76+
Lists and connections require a little extra consideration. By default, each _item_ in a list is registered with the cache, but when new items are created, they are unknown to the cache and therefore don't invalidate the cached result. There are two main approaches to address this.
77+
78+
### `has_many` lists
79+
80+
In order to effectively bust the cache, items that belong to the list of "parent" object should __update the parent__ (eg, Rails `.touch`) whenever they're created, destroyed, or updated. For example, if there's a list of players on a team:
7781

7882
```graphql
7983
{
@@ -92,6 +96,71 @@ With Rails, you can accomplish this with:
9296
belongs_to :team, touch: true
9397
```
9498

99+
### Top-level lists
100+
101+
For `ActiveRecord::Relation`s _without_ a "parent" object, you can use `GraphQL::Enterprise::ObjectCache::CacheableRelation` to make a synthetic cache entry for the _whole_ relation. To use this class, make a subclass and implement `def items`, for example:
102+
103+
```ruby
104+
class AllTeams < GraphQL::Enterprise::ObjectCache::CacheableRelation
105+
def items(division: nil)
106+
teams = Team.all
107+
if division
108+
teams = teams.where(division: division)
109+
end
110+
teams
111+
end
112+
end
113+
```
114+
115+
Then, in your resolver, use your new class to retrieve the items:
116+
117+
```ruby
118+
class Query < GraphQL::Schema::Object
119+
field :teams, Team.connection_type do
120+
argument :division, Division, required: false
121+
end
122+
123+
def teams(division: nil)
124+
AllTeams.items_for(self, division: division)
125+
end
126+
end
127+
```
128+
129+
Finally, you'll need to handle `CacheableRelation`s in your object identification methods, for example:
130+
131+
```ruby
132+
class MySchema < GraphQL::Schema
133+
# ...
134+
def self.id_from_object(object, type, ctx)
135+
if object.is_a?(GraphQL::Enterprise::ObjectCache::CacheableRelation)
136+
object.id
137+
else
138+
# The rest of your id_from_object logic here...
139+
end
140+
end
141+
142+
def self.object_from_id(id, ctx)
143+
if (cacheable_rel = GraphQL::Enterprise::ObjectCache::CacheableRelation.find?(id))
144+
cacheable_rel
145+
else
146+
# The rest of your object_from_id logic here...
147+
end
148+
end
149+
end
150+
```
151+
152+
In this example, `AllTeams` takes care of integrating with the cache:
153+
154+
- It implements `#id` to create a cache-friendly, stable global ID
155+
- It implements `#to_param` to create a cache fingerprint (using Rails's `#cache_key` under the hood)
156+
- It implements `.find?` to retrieve the list based on its ID
157+
158+
This way, if a `Team` is created, the cached result will be invalidated and a fresh result will be created.
159+
160+
Alternatively (or additionally), you could use a `ttl:` to expire cached results after a certain duration, just to be sure that results are eventually expired.
161+
162+
### Connections
163+
95164
By default, connection-related objects (like `*Connection` and `*Edge` types) "inherit" cacheability from their node types. You can override this in your base classes as long as `GraphQL::Enterprise::ObjectCache::ObjectIntegration` is included in the inheritance chain somewhere.
96165

97166
## Caching Introspection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4e7776668f4e8f7897e41a2d8844c1684e914c477f1eb8f4aa204db9dca56c219f0a6d631e253b23d951d15a8e8bf106f786925655491a1f562ac270561f8f81

0 commit comments

Comments
 (0)