Skip to content

Commit 7d647c3

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

File tree

2 files changed

+78
-12
lines changed

2 files changed

+78
-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: 75 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,68 @@
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_count, :exist_count
100+
101+
def initialize
102+
super
103+
@read_multi_count = 0
104+
@exist_count = 0
105+
end
106+
107+
def read_multi(*names)
108+
@read_multi_count += 1
109+
puts "[LoggingMemoryStore] read_multi: #{names.join(', ')}"
110+
super
111+
end
112+
113+
def exist?(name, options = nil)
114+
@exist_count += 1
115+
puts "[LoggingMemoryStore] exist?: #{name}"
116+
super
117+
end
118+
end
119+
120+
let(:cache_store) { LoggingMemoryStore.new }
121+
122+
before do
123+
GraphQL::FragmentCache.cache_store = cache_store
124+
end
125+
126+
context 'without dataloader option' do
127+
it 'calls 1 read_multi' do
128+
post '/graphql', params: {
129+
query: query,
130+
variables: { useCache: true, useDataloader: false }.to_json
131+
}
132+
133+
expect(response).to have_http_status(:success)
134+
json_response = JSON.parse(response.body)
135+
expect(json_response["errors"]).to be_nil
136+
expect(json_response["data"]["users"]).to be_present
137+
138+
expect(cache_store.read_multi_count).to be 1
139+
expect(cache_store.exist_count).to be 0
140+
end
141+
end
142+
143+
context 'with dataloader option' do
144+
it 'calls 1 read_multi and N exist?' do
145+
post '/graphql', params: {
146+
query: query,
147+
variables: { useCache: true, useDataloader: true }.to_json
148+
}
149+
150+
expect(response).to have_http_status(:success)
151+
json_response = JSON.parse(response.body)
152+
expect(json_response["errors"]).to be_nil
153+
expect(json_response["data"]["users"]).to be_present
154+
155+
expect(cache_store.read_multi_count).to be 1
156+
expect(cache_store.exist_count).to be users.size
157+
end
158+
end
159+
end
95160
end

0 commit comments

Comments
 (0)