Skip to content
This repository was archived by the owner on Oct 26, 2022. It is now read-only.

Commit 837bb75

Browse files
committed
Implement ActiveRecord test polygon; Reproduce bug with association cache
1 parent cf6dd67 commit 837bb75

File tree

11 files changed

+162
-25
lines changed

11 files changed

+162
-25
lines changed

Gemfile.lock

+20-2
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,34 @@ PATH
77
GEM
88
remote: https://rubygems.org/
99
specs:
10+
activemodel (6.0.2.1)
11+
activesupport (= 6.0.2.1)
12+
activerecord (6.0.2.1)
13+
activemodel (= 6.0.2.1)
14+
activesupport (= 6.0.2.1)
15+
activesupport (6.0.2.1)
16+
concurrent-ruby (~> 1.0, >= 1.0.2)
17+
i18n (>= 0.7, < 2)
18+
minitest (~> 5.1)
19+
tzinfo (~> 1.1)
20+
zeitwerk (~> 2.2)
1021
appraisal (2.2.0)
1122
bundler
1223
rake
1324
thor (>= 0.14.0)
1425
codeclimate-test-reporter (1.0.9)
1526
simplecov (<= 0.13)
1627
coderay (1.1.2)
28+
concurrent-ruby (1.1.5)
1729
diff-lcs (1.3)
1830
docile (1.1.5)
1931
graphql (1.9.3)
32+
i18n (1.7.0)
33+
concurrent-ruby (~> 1.0)
2034
json (2.2.0)
2135
method_source (0.9.2)
2236
mini_cache (1.1.0)
23-
promise.rb (0.7.4)
37+
minitest (5.13.0)
2438
pry (0.12.2)
2539
coderay (~> 1.1.0)
2640
method_source (~> 0.9.0)
@@ -46,16 +60,20 @@ GEM
4660
simplecov-html (0.10.2)
4761
sqlite3 (1.4.0)
4862
thor (0.20.3)
63+
thread_safe (0.3.6)
64+
tzinfo (1.2.5)
65+
thread_safe (~> 0.1)
66+
zeitwerk (2.2.2)
4967

5068
PLATFORMS
5169
ruby
5270

5371
DEPENDENCIES
72+
activerecord
5473
appraisal
5574
codeclimate-test-reporter
5675
graphql-cache!
5776
mini_cache
58-
promise.rb
5977
pry
6078
rake (~> 10.0)
6179
rspec (~> 3.0)

graphql-cache.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
2828
s.add_development_dependency 'rake', '~> 10.0'
2929
s.add_development_dependency 'rspec', '~> 3.0'
3030
s.add_development_dependency 'sequel'
31+
s.add_development_dependency 'activerecord'
3132
s.add_development_dependency 'simplecov'
3233
s.add_development_dependency 'sqlite3'
3334

spec/features/connections_spec.rb

+56-20
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
require 'spec_helper'
22

3-
def execute(query, context = {})
4-
CacheSchema.execute(query, context: context)
5-
end
6-
73
RSpec.describe 'caching connection fields' do
84
class StubLogger < Logger
95
def initialize
@@ -19,7 +15,7 @@ def messages
1915
let(:query) do
2016
%Q{
2117
{
22-
customer(id: #{Customer.last.id}) {
18+
customer(id: #{customer.id}) {
2319
orders {
2420
edges {
2521
node {
@@ -35,31 +31,71 @@ def messages
3531
let(:sql_logger) do
3632
StubLogger.new.tap do |logger|
3733
logger.formatter = proc do |_severity, _datetime, _progname, msg|
38-
raw_sql = msg.match(/\(.*\)\s(?<sql>.*)/)["sql"]
34+
raw_sql = msg.match(/.*(?<sql>SELECT .*)/)["sql"]
3935

4036
"#{raw_sql}\n"
4137
end
4238
end
4339
end
4440

45-
before { DB.logger = sql_logger }
41+
shared_examples "be a correct cold and warm" do
42+
let(:reference) do
43+
{"data" => {"customer" => {"orders" => {"edges" => [{"node" => {"id" =>1 }}, {"node" => {"id" => 2}}, {"node" => {"id" => 3}}]}}}}
44+
end
45+
46+
it 'produces the same result on miss or hit' do
47+
cold_results = execute(query)
48+
warm_results = execute(query)
49+
50+
expect(cold_results).to eq(reference)
51+
expect(cold_results).to eq warm_results
52+
end
53+
end
54+
55+
describe 'Seqeul' do
56+
def execute(query, context = {})
57+
CacheSchema.execute(query, context: context)
58+
end
59+
let(:customer) { Customer.last }
60+
61+
before { DB.logger = sql_logger }
62+
63+
it_behaves_like "be a correct cold and warm"
4664

47-
it 'produces the same result on miss or hit' do
48-
cold_results = execute(query)
49-
warm_results = execute(query)
65+
it 'calls sql engine only one time per cached field' do
66+
5.times { execute(query) }
5067

51-
expect(cold_results).to eq warm_results
68+
expect(sql_logger.messages).to eq(
69+
<<~SQL
70+
SELECT * FROM `customers` ORDER BY `id` DESC LIMIT 1
71+
SELECT * FROM `customers` WHERE `id` = '1'
72+
SELECT * FROM `orders` WHERE (`orders`.`customer_id` = 1)
73+
SQL
74+
)
75+
end
5276
end
5377

54-
it 'calls sql engine only one time per cached field' do
55-
5.times { execute(query) }
78+
describe 'ActiveRecord' do
79+
def execute(query, context = {})
80+
AR::CacheSchema.execute(query, context: context)
81+
end
82+
83+
let(:customer) { AR::Customer.last }
5684

57-
expect(sql_logger.messages).to eq(
58-
<<~SQL
59-
SELECT * FROM `customers` ORDER BY `id` DESC LIMIT 1
60-
SELECT * FROM `customers` WHERE `id` = '1'
61-
SELECT * FROM `orders` WHERE (`orders`.`customer_id` = 1)
62-
SQL
63-
)
85+
before { ActiveRecord::Base.logger = sql_logger }
86+
87+
it_behaves_like "be a correct cold and warm"
88+
89+
it 'calls sql engine only one time per cached field' do
90+
5.times { execute(query) }
91+
92+
expect(sql_logger.messages).to eq(
93+
<<~SQL
94+
SELECT "customers".* FROM "customers" ORDER BY "customers"."id" DESC LIMIT ? [["LIMIT", 1]]
95+
SELECT "customers".* FROM "customers" WHERE "customers"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
96+
SELECT "orders".* FROM "orders" WHERE "orders"."customer_id" = ? [["customer_id", 1]]
97+
SQL
98+
)
99+
end
64100
end
65101
end

spec/spec_helper.rb

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'active_record' # should be before graphql-ruby
12
require 'bundler/setup'
23
require 'pry'
34

@@ -36,6 +37,7 @@
3637
# required after GraphQL::Cache initialization because dev
3738
# schema uses cache and logger objects from it.
3839
require_relative '../test_schema'
40+
require_relative '../test_schema/active_record/init'
3941

4042
config.include TestMacros
4143
config.extend TestMacros::ClassMethods

spec/support/test_cache.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
class TestCache
44
def write(key, doc, opts={})
5-
cache[key] = doc
5+
# we duplicate the value to get rid of ruby object level caching
6+
cache[key] = ::Marshal.load(::Marshal.dump(doc))
67
end
78

89
def read(key)
9-
cache[key]
10+
::Marshal.load(::Marshal.dump(cache[key]))
1011
end
1112

1213
def cache
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module AR
2+
class Factories
3+
def self.bootstrap
4+
customer = AR::Customer.create(
5+
display_name: 'Michael',
6+
email: 'michael@example.com'
7+
)
8+
9+
AR::Order.create(customer_id: customer.id, number: new_num, total_price_cents: 1399)
10+
AR::Order.create(customer_id: customer.id, number: new_num, total_price_cents: 1399)
11+
AR::Order.create(customer_id: customer.id, number: new_num, total_price_cents: 1399)
12+
end
13+
14+
def self.new_num
15+
AR::Order.count + 1000
16+
end
17+
end
18+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
require 'benchmark'
2+
3+
module AR
4+
class BaseType < ::BaseType; end
5+
class OrderType < ::OrderType; end
6+
7+
class CustomerType < ::CustomerType; end
8+
9+
class QueryType < ::QueryType
10+
def customer(id:)
11+
AR::Customer.find(id)
12+
end
13+
end
14+
15+
class CacheSchema < ::CacheSchema
16+
query AR::QueryType
17+
use GraphQL::Cache
18+
19+
def self.resolve_type(_type, obj, _ctx)
20+
"AR::#{obj.class.name}Type"
21+
end
22+
end
23+
end

test_schema/active_record/init.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'logger'
2+
3+
require_relative './schema'
4+
require_relative './models'
5+
require_relative './graphql_schema'
6+
require_relative './factories'
7+
8+
ActiveRecord::Base.logger = GraphQL::Cache.logger
9+
AR::Factories.bootstrap

test_schema/active_record/models.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module AR
2+
class Order < ActiveRecord::Base
3+
belongs_to :customer
4+
end
5+
6+
class Customer < ActiveRecord::Base
7+
has_many :orders
8+
end
9+
end

test_schema/active_record/schema.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'active_record'
2+
3+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
4+
5+
ActiveRecord::Schema.define do
6+
self.verbose = false
7+
8+
create_table :schema_migrations, force: true
9+
10+
create_table :customers, force: true do |t|
11+
t.string :display_name
12+
t.string :email
13+
end
14+
15+
create_table :orders, force: true do |t|
16+
t.integer :customer_id
17+
t.integer :number
18+
t.integer :total_price_cents
19+
end
20+
end

test_schema/factories.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Factories
1+
class Factories
22
def self.bootstrap
33
customer = Customer.create(
44
display_name: 'Michael',

0 commit comments

Comments
 (0)