Skip to content

Commit 7d65f9c

Browse files
committed
Add test to check if exist? is called N times
1 parent 8b17847 commit 7d65f9c

File tree

2 files changed

+96
-12
lines changed

2 files changed

+96
-12
lines changed

app/graphql/types/user_type.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ class UserType < Types::BaseObject
77

88
field :posts, [ Types::PostType ], null: false do
99
argument :use_cache, Boolean, required: true
10+
argument :use_dataloader, Boolean, required: true
1011
end
11-
def posts(use_cache:)
12+
def posts(use_cache:, use_dataloader:)
1213
if use_cache
13-
cache_fragment(expires_in: 1.minute, dataloader: true) do
14+
cache_fragment(expires_in: 1.minute, dataloader: use_dataloader) do
1415
dataloader.with(Sources::PostsByUser).load(object.id)
1516
end
1617
else

spec/requests/graphql_spec.rb

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
require 'rails_helper'
22

33
RSpec.describe 'FragmentCache + Dataloader N+1 Investigation', type: :request do
4+
let(:users) { 5.times.map { |i| User.create!(name: "User #{i}") } }
5+
46
before do
5-
user1 = User.create!(name: "Alice")
6-
user2 = User.create!(name: "Bob")
7-
5.times do |i|
8-
user1.posts.create!(title: "Alice Post #{i}")
9-
user2.posts.create!(title: "Bob Post #{i}")
7+
users.each do |user|
8+
2.times do |i|
9+
user.posts.create!(title: "Post #{i}")
10+
end
1011
end
1112
end
1213

1314
let(:query) do
1415
<<~GRAPHQL
15-
query($useCache: Boolean!) {
16+
query($useCache: Boolean!, $useDataloader: Boolean!) {
1617
users {
1718
id
1819
name
19-
posts(useCache: $useCache) {
20+
posts(useCache: $useCache, useDataloader: $useDataloader) {
2021
id
2122
title
2223
}
@@ -25,7 +26,7 @@
2526
GRAPHQL
2627
end
2728

28-
describe 'N+1 for database' do
29+
describe.skip 'N+1 for database' do
2930
context 'when cache_fragment is disabled' do
3031
it 'should have minimal query count with effective batching' do
3132
queries = []
@@ -40,7 +41,7 @@
4041
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
4142
post '/graphql', params: {
4243
query: query,
43-
variables: { useCache: false }.to_json
44+
variables: { useCache: false, useDataloader: false }.to_json
4445
}
4546
end
4647

@@ -74,7 +75,7 @@
7475
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
7576
post '/graphql', params: {
7677
query: query,
77-
variables: { useCache: true }.to_json
78+
variables: { useCache: true, useDataloader: true }.to_json
7879
}
7980
end
8081

@@ -92,4 +93,86 @@
9293
end
9394
end
9495
end
96+
97+
describe 'N+1 for cache' do
98+
class LoggingMemoryStore < ActiveSupport::Cache::MemoryStore
99+
attr_reader :read_multi_calls, :exist_calls
100+
101+
def initialize
102+
super
103+
@read_multi_calls = []
104+
@exist_calls = []
105+
end
106+
107+
def read_multi(*names)
108+
@read_multi_calls << names
109+
super
110+
end
111+
112+
def exist?(name, options = nil)
113+
@exist_calls << name
114+
super
115+
end
116+
end
117+
118+
let(:cache_store) { LoggingMemoryStore.new }
119+
120+
before do
121+
GraphQL::FragmentCache.cache_store = cache_store
122+
end
123+
124+
context 'without dataloader option' do
125+
it 'calls 1 read_multi' do
126+
post '/graphql', params: {
127+
query: query,
128+
variables: { useCache: true, useDataloader: false }.to_json
129+
}
130+
131+
expect(response).to have_http_status(:success)
132+
json_response = JSON.parse(response.body)
133+
expect(json_response["errors"]).to be_nil
134+
expect(json_response["data"]["users"]).to be_present
135+
136+
expect(cache_store.read_multi_calls.size).to be 1
137+
expect(cache_store.exist_calls.size).to be 0
138+
139+
puts "\n=== Cache operations without dataloader ==="
140+
puts "read_multi calls:"
141+
cache_store.read_multi_calls.each.with_index(1) do |names, i|
142+
puts " #{i}. #{names.join(', ')}"
143+
end
144+
puts "exist? calls:"
145+
cache_store.exist_calls.each.with_index(1) do |name, i|
146+
puts " #{i}. #{name}"
147+
end
148+
end
149+
end
150+
151+
context 'with dataloader option' do
152+
it 'calls 1 read_multi and N exist?' do
153+
post '/graphql', params: {
154+
query: query,
155+
variables: { useCache: true, useDataloader: true }.to_json
156+
}
157+
158+
expect(response).to have_http_status(:success)
159+
json_response = JSON.parse(response.body)
160+
expect(json_response["errors"]).to be_nil
161+
expect(json_response["data"]["users"]).to be_present
162+
163+
expect(cache_store.read_multi_calls.size).to be 1
164+
expect(cache_store.exist_calls.size).to be users.size
165+
166+
puts "\n=== Cache operations with dataloader ==="
167+
puts "read_multi calls:"
168+
cache_store.read_multi_calls.each.with_index(1) do |names, i|
169+
puts " #{i}. #{names.join(', ')}"
170+
end
171+
puts "exist? calls:"
172+
cache_store.exist_calls.each.with_index(1) do |name, i|
173+
puts " #{i}. #{name}"
174+
end
175+
end
176+
end
177+
end
95178
end

0 commit comments

Comments
 (0)