Skip to content

Commit

Permalink
Dataloader support
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitryTsepelev committed Jan 6, 2025
1 parent 91704e9 commit 668ebc1
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## master

- [PR#130](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/130) Dataloader support ([@DmitryTsepelev][])
- [PR#125](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/125) Introduce cache lookup instrumentation hook ([@danielhartnell][])

## 1.20.5 (2024-11-02)
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,34 @@ class QueryType < BaseObject
end
```

## Dataloader

If you are using [Dataloader](https://graphql-ruby.org/dataloader/overview.html), you will need to let the gem know using `dataloader: true`:

```ruby
class PostType < BaseObject
field :author, User, null: false

def author
cache_fragment(dataloader: true) do
dataloader.with(AuthorDataloaderSource).load(object.id)
end
end
end

# or

class PostType < BaseObject
field :author, User, null: false, cache_fragment: {dataloader: true}

def author
dataloader.with(AuthorDataloaderSource).load(object.id)
end
end
```

The problem is that I didn't find a way to detect that dataloader (and, therefore, Fiber) is used, and the block is forced to resolve, causing the N+1 inside the Dataloader Source class.

## How to use `#cache_fragment` in extensions (and other places where context is not available)

If you want to call `#cache_fragment` from places other that fields or resolvers, you'll need to pass `context` explicitly and turn on `raw_value` support. For instance, let's take a look at this extension:
Expand Down
5 changes: 5 additions & 0 deletions lib/graphql/fragment_cache/object_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ def cache_fragment(object_to_cache = NO_OBJECT, **options, &block)

fragment = Fragment.new(context_to_use, **options)

if options.delete(:dataloader)
object_to_cache = block.call
block = nil
end

GraphQL::FragmentCache::Schema::LazyCacheResolver.new(fragment, context_to_use, object_to_cache, &block)
end
end
Expand Down
53 changes: 53 additions & 0 deletions spec/graphql/fragment_cache/object_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,59 @@ def post(id:, expires_in: nil)
end
end

describe "caching fields with dataloader" do
let(:query) do
<<~GQL
query GetPosts {
posts {
id
dataloaderCachedAuthor {
name
}
}
}
GQL
end

let(:schema) do
build_schema do
use GraphQL::Dataloader
query(Types::Query)
end
end

let(:user1) { User.new(id: 1, name: "User #1") }
let(:user2) { User.new(id: 2, name: "User #2") }

let!(:post1) { Post.create(id: 1, title: "object test 1", author: user1) }
let!(:post2) { Post.create(id: 2, title: "object test 2", author: user2) }

before do
allow(User).to receive(:find_by_post_ids).and_call_original

# warmup cache
execute_query
expect(User).to have_received(:find_by_post_ids).with([post1.id, post2.id])

# make objects dirty
user1.name = "User #1 new"
user2.name = "User #2 new"
end

it "returns cached results" do
expect(execute_query.dig("data", "posts")).to eq([
{
"id" => "1",
"dataloaderCachedAuthor" => {"name" => "User #1"}
},
{
"id" => "2",
"dataloaderCachedAuthor" => {"name" => "User #2"}
}
])
end
end

describe "conditional caching" do
let(:schema) do
field_resolver = resolver
Expand Down
6 changes: 6 additions & 0 deletions spec/support/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ class User
attr_reader :id
attr_accessor :name

class << self
def find_by_post_ids(post_ids)
post_ids.map { |id| Post.find(id).author }
end
end

def initialize(id:, name:)
@id = id
@name = name
Expand Down
13 changes: 13 additions & 0 deletions spec/support/test_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ def perform(posts)
end
end

class AuthorDataloaderSource < GraphQL::Dataloader::Source
def fetch(post_ids)
User.find_by_post_ids(post_ids)
end
end

module Types
class Base < GraphQL::Schema::Object
include GraphQL::FragmentCache::Object
Expand Down Expand Up @@ -41,6 +47,7 @@ class Post < Base
field :cached_author, User, null: false
field :batched_cached_author, User, null: false
field :cached_author_inside_batch, User, null: false
field :dataloader_cached_author, User, null: false

field :meta, String, null: true

Expand All @@ -60,6 +67,12 @@ def cached_author_inside_batch
cache_fragment(author, context: context)
end
end

def dataloader_cached_author
cache_fragment(dataloader: true) do
dataloader.with(AuthorDataloaderSource).load(object.id)
end
end
end

class PostInput < GraphQL::Schema::InputObject
Expand Down

0 comments on commit 668ebc1

Please sign in to comment.