identity_cache 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +231 -0
- data/Rakefile +16 -0
- data/identity_cache.gemspec +24 -0
- data/lib/belongs_to_caching.rb +39 -0
- data/lib/identity_cache.rb +570 -0
- data/lib/identity_cache/version.rb +3 -0
- data/lib/memoized_cache_proxy.rb +71 -0
- data/test/attribute_cache_test.rb +73 -0
- data/test/denormalized_has_many_test.rb +89 -0
- data/test/denormalized_has_one_test.rb +99 -0
- data/test/fetch_multi_test.rb +144 -0
- data/test/fetch_test.rb +108 -0
- data/test/helpers/cache.rb +60 -0
- data/test/helpers/database_connection.rb +41 -0
- data/test/identity_cache_test.rb +17 -0
- data/test/index_cache_test.rb +96 -0
- data/test/memoized_cache_proxy_test.rb +60 -0
- data/test/normalized_belongs_to_test.rb +46 -0
- data/test/normalized_has_many_test.rb +125 -0
- data/test/recursive_denormalized_has_many_test.rb +97 -0
- data/test/save_test.rb +64 -0
- data/test/test_helper.rb +73 -0
- metadata +154 -0
data/test/fetch_test.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class FetchTest < IdentityCache::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
Record.cache_index :title, :unique => true
|
7
|
+
Record.cache_index :id, :title, :unique => true
|
8
|
+
|
9
|
+
@record = Record.new
|
10
|
+
@record.id = 1
|
11
|
+
@record.title = 'bob'
|
12
|
+
@blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:1"
|
13
|
+
@index_key = "IDC:index:Record:title:#{cache_hash('bob')}"
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def test_fetch_cache_hit
|
18
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(@record)
|
19
|
+
|
20
|
+
assert_equal @record, Record.fetch(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_exists_with_identity_cache_when_cache_hit
|
24
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(@record)
|
25
|
+
|
26
|
+
assert Record.exists_with_identity_cache?(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_exists_with_identity_cache_when_cache_miss_and_in_db
|
30
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(nil)
|
31
|
+
Record.expects(:find_by_id).with(1, :include => []).returns(@record)
|
32
|
+
|
33
|
+
assert Record.exists_with_identity_cache?(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_exists_with_identity_cache_when_cache_miss_and_not_in_db
|
37
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(nil)
|
38
|
+
Record.expects(:find_by_id).with(1, :include => []).returns(nil)
|
39
|
+
|
40
|
+
assert !Record.exists_with_identity_cache?(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_fetch_miss
|
44
|
+
Record.expects(:find_by_id).with(1, :include => []).returns(@record)
|
45
|
+
|
46
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(nil)
|
47
|
+
IdentityCache.cache.expects(:write).with(@blob_key, @record)
|
48
|
+
|
49
|
+
assert_equal @record, Record.fetch(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_fetch_by_id_not_found_should_return_nil
|
53
|
+
nonexistent_record_id = 10
|
54
|
+
IdentityCache.cache.expects(:write).with(@blob_key + '0', IdentityCache::CACHED_NIL)
|
55
|
+
|
56
|
+
assert_equal nil, Record.fetch_by_id(nonexistent_record_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_fetch_not_found_should_raise
|
60
|
+
nonexistent_record_id = 10
|
61
|
+
IdentityCache.cache.expects(:write).with(@blob_key + '0', IdentityCache::CACHED_NIL)
|
62
|
+
|
63
|
+
assert_raises(ActiveRecord::RecordNotFound) { Record.fetch(nonexistent_record_id) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_cached_nil_expiry_on_record_creation
|
67
|
+
key = @record.primary_cache_index_key
|
68
|
+
|
69
|
+
assert_equal nil, Record.fetch_by_id(@record.id)
|
70
|
+
assert_equal IdentityCache::CACHED_NIL, IdentityCache.cache.read(key)
|
71
|
+
|
72
|
+
@record.save!
|
73
|
+
assert_nil IdentityCache.cache.read(key)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_fetch_by_title_hit
|
77
|
+
# Read record with title bob
|
78
|
+
IdentityCache.cache.expects(:read).with(@index_key).returns(nil)
|
79
|
+
|
80
|
+
# - not found, use sql, SELECT id FROM records WHERE title = '...' LIMIT 1"
|
81
|
+
Record.connection.expects(:select_value).returns(1)
|
82
|
+
|
83
|
+
# cache sql result
|
84
|
+
IdentityCache.cache.expects(:write).with(@index_key, 1)
|
85
|
+
|
86
|
+
# got id, do memcache lookup on that, hit -> done
|
87
|
+
IdentityCache.cache.expects(:read).with(@blob_key).returns(@record)
|
88
|
+
|
89
|
+
assert_equal @record, Record.fetch_by_title('bob')
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_fetch_by_title_stores_idcnil
|
93
|
+
Record.connection.expects(:select_value).once.returns(nil)
|
94
|
+
Rails.cache.expects(:write).with(@index_key, IdentityCache::CACHED_NIL)
|
95
|
+
Rails.cache.expects(:read).with(@index_key).times(3).returns(nil, IdentityCache::CACHED_NIL, IdentityCache::CACHED_NIL)
|
96
|
+
assert_equal nil, Record.fetch_by_title('bob') # select_value => nil
|
97
|
+
|
98
|
+
assert_equal nil, Record.fetch_by_title('bob') # returns cached nil
|
99
|
+
assert_equal nil, Record.fetch_by_title('bob') # returns cached nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_fetch_by_bang_method
|
103
|
+
Record.connection.expects(:select_value).returns(nil)
|
104
|
+
assert_raises ActiveRecord::RecordNotFound do
|
105
|
+
Record.fetch_by_title!('bob')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Rails
|
2
|
+
class Cache
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@cache = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def fetch(e)
|
9
|
+
if @cache.key?(e)
|
10
|
+
return read(e)
|
11
|
+
else
|
12
|
+
a = yield
|
13
|
+
write(e,a)
|
14
|
+
return a
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear
|
19
|
+
@cache.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(a,b)
|
23
|
+
@cache[a] = b
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(a)
|
27
|
+
@cache.delete(a)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read(a)
|
31
|
+
@cache[a]
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_multi(*keys)
|
35
|
+
keys.reduce({}) do |hash, key|
|
36
|
+
hash[key] = @cache[key]
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.cache
|
43
|
+
@@cache ||= Cache.new
|
44
|
+
end
|
45
|
+
|
46
|
+
class Logger
|
47
|
+
def info(string)
|
48
|
+
end
|
49
|
+
|
50
|
+
def debug(string)
|
51
|
+
end
|
52
|
+
|
53
|
+
def error(string)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.logger
|
58
|
+
@logger = Logger.new
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DatabaseConnection
|
2
|
+
def self.setup
|
3
|
+
ActiveRecord::Base.establish_connection(DATABASE_CONFIG)
|
4
|
+
ActiveRecord::Base.connection
|
5
|
+
rescue
|
6
|
+
ActiveRecord::Base.establish_connection(DATABASE_CONFIG.merge('database' => nil))
|
7
|
+
ActiveRecord::Base.connection.create_database(DATABASE_CONFIG['database'])
|
8
|
+
ActiveRecord::Base.establish_connection(DATABASE_CONFIG)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.drop_tables
|
12
|
+
TABLES.keys.each do |table|
|
13
|
+
ActiveRecord::Base.connection.drop_table(table) if ActiveRecord::Base.connection.table_exists?(table)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create_tables
|
18
|
+
TABLES.each do |table, fields|
|
19
|
+
ActiveRecord::Base.connection.create_table(table) do |t|
|
20
|
+
fields.each do |column_type, *args|
|
21
|
+
t.send(column_type, *args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
TABLES = {
|
28
|
+
:polymorphic_records => [[:string, :owner_type], [:integer, :owner_id], [:timestamps]],
|
29
|
+
:deeply_associated_records => [[:string, :name], [:integer, :associated_record_id]],
|
30
|
+
:associated_records => [[:string, :name], [:integer, :record_id]],
|
31
|
+
:not_cached_records => [[:string, :name], [:integer, :record_id]],
|
32
|
+
:records => [[:string, :title], [:timestamps]]
|
33
|
+
}
|
34
|
+
|
35
|
+
DATABASE_CONFIG = {
|
36
|
+
'adapter' => 'mysql2',
|
37
|
+
'database' => 'identity_cache_test',
|
38
|
+
'host' => '127.0.0.1',
|
39
|
+
'username' => 'root'
|
40
|
+
}
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class IdentityCacheTest < IdentityCache::TestCase
|
4
|
+
|
5
|
+
class BadModel < ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_identity_cache_raises_if_loaded_twice
|
9
|
+
assert_raises(IdentityCache::AlreadyIncludedError) do
|
10
|
+
BadModel.class_eval do
|
11
|
+
include IdentityCache
|
12
|
+
include IdentityCache
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ExpirationTest < IdentityCache::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
@record = Record.new
|
7
|
+
@record.id = 1
|
8
|
+
@record.title = 'bob'
|
9
|
+
@cache_key = "IDC:index:Record:title:#{cache_hash(@record.title)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_unique_index_caches_nil
|
13
|
+
Record.cache_index :title, :unique => true
|
14
|
+
assert_equal nil, Record.fetch_by_title('bob')
|
15
|
+
assert_equal IdentityCache::CACHED_NIL, IdentityCache.cache.read(@cache_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_unique_index_expired_by_new_record
|
19
|
+
Record.cache_index :title, :unique => true
|
20
|
+
IdentityCache.cache.write(@cache_key, IdentityCache::CACHED_NIL)
|
21
|
+
@record.save!
|
22
|
+
assert_equal nil, IdentityCache.cache.read(@cache_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_unique_index_filled_on_fetch_by
|
26
|
+
Record.cache_index :title, :unique => true
|
27
|
+
@record.save!
|
28
|
+
assert_equal @record, Record.fetch_by_title('bob')
|
29
|
+
assert_equal @record.id, IdentityCache.cache.read(@cache_key)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_unique_index_expired_by_updated_record
|
33
|
+
Record.cache_index :title, :unique => true
|
34
|
+
@record.save!
|
35
|
+
IdentityCache.cache.write(@cache_key, @record.id)
|
36
|
+
|
37
|
+
@record.title = 'robert'
|
38
|
+
new_cache_key = "IDC:index:Record:title:#{cache_hash(@record.title)}"
|
39
|
+
IdentityCache.cache.write(new_cache_key, IdentityCache::CACHED_NIL)
|
40
|
+
@record.save!
|
41
|
+
assert_equal nil, IdentityCache.cache.read(@cache_key)
|
42
|
+
assert_equal nil, IdentityCache.cache.read(new_cache_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def test_non_unique_index_caches_empty_result
|
47
|
+
Record.cache_index :title
|
48
|
+
assert_equal [], Record.fetch_by_title('bob')
|
49
|
+
assert_equal [], IdentityCache.cache.read(@cache_key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_non_unique_index_expired_by_new_record
|
53
|
+
Record.cache_index :title
|
54
|
+
IdentityCache.cache.write(@cache_key, [])
|
55
|
+
@record.save!
|
56
|
+
assert_equal nil, IdentityCache.cache.read(@cache_key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_non_unique_index_filled_on_fetch_by
|
60
|
+
Record.cache_index :title
|
61
|
+
@record.save!
|
62
|
+
assert_equal [@record], Record.fetch_by_title('bob')
|
63
|
+
assert_equal [@record.id], IdentityCache.cache.read(@cache_key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_non_unique_index_fetches_multiple_records
|
67
|
+
Record.cache_index :title
|
68
|
+
@record.save!
|
69
|
+
record2 = Record.create(:id => 2, :title => 'bob')
|
70
|
+
|
71
|
+
assert_equal [@record, record2], Record.fetch_by_title('bob')
|
72
|
+
assert_equal [1, 2], IdentityCache.cache.read(@cache_key)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_non_unique_index_expired_by_updating_record
|
76
|
+
Record.cache_index :title
|
77
|
+
@record.save!
|
78
|
+
IdentityCache.cache.write(@cache_key, [@record.id])
|
79
|
+
|
80
|
+
@record.title = 'robert'
|
81
|
+
new_cache_key = "IDC:index:Record:title:#{cache_hash(@record.title)}"
|
82
|
+
IdentityCache.cache.write(new_cache_key, [])
|
83
|
+
@record.save!
|
84
|
+
assert_equal nil, IdentityCache.cache.read(@cache_key)
|
85
|
+
assert_equal nil, IdentityCache.cache.read(new_cache_key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_non_unique_index_expired_by_destroying_record
|
89
|
+
Record.cache_index :title
|
90
|
+
@record.save!
|
91
|
+
IdentityCache.cache.write(@cache_key, [@record.id])
|
92
|
+
@record.destroy
|
93
|
+
assert_equal nil, IdentityCache.cache.read(@cache_key)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class MemoizedCacheProxyTest < IdentityCache::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
IdentityCache.cache_backend = Rails.cache
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_chaging_default_cache
|
10
|
+
IdentityCache.cache_backend = ActiveSupport::Cache::MemoryStore.new
|
11
|
+
IdentityCache.cache.write('foo', 'bar')
|
12
|
+
assert_equal 'bar', IdentityCache.cache.read('foo')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_read_should_short_circuit_on_memoized_values
|
16
|
+
Rails.cache.expects(:read).never
|
17
|
+
|
18
|
+
IdentityCache.cache.with_memoization do
|
19
|
+
IdentityCache.cache.write('foo', 'bar')
|
20
|
+
assert_equal 'bar', IdentityCache.cache.read('foo')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_read_should_try_memcached_on_not_memoized_values
|
25
|
+
Rails.cache.expects(:read).with('foo').returns('bar')
|
26
|
+
|
27
|
+
IdentityCache.cache.with_memoization do
|
28
|
+
assert_equal 'bar', IdentityCache.cache.read('foo')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_write_should_memoize_values
|
33
|
+
Rails.cache.expects(:read).never
|
34
|
+
Rails.cache.expects(:write).with('foo', 'bar')
|
35
|
+
|
36
|
+
|
37
|
+
IdentityCache.cache.with_memoization do
|
38
|
+
IdentityCache.cache.write('foo', 'bar')
|
39
|
+
assert_equal 'bar', IdentityCache.cache.read('foo')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_read_multi_with_partially_memoized_should_read_missing_keys_from_memcache
|
44
|
+
IdentityCache.cache.write('foo', 'bar')
|
45
|
+
Rails.cache.write('fooz', 'baz')
|
46
|
+
|
47
|
+
IdentityCache.cache.with_memoization do
|
48
|
+
assert_equal({'foo' => 'bar', 'fooz' => 'baz'}, IdentityCache.cache.read_multi('foo', 'fooz'))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_with_memoization_should_not_clear_rails_cache_when_the_block_ends
|
53
|
+
IdentityCache.cache.with_memoization do
|
54
|
+
IdentityCache.cache.write('foo', 'bar')
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_equal 'bar', Rails.cache.read('foo')
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class NormalizedBelongsToTest < IdentityCache::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
AssociatedRecord.cache_belongs_to :record, :embed => false
|
7
|
+
|
8
|
+
@parent_record = Record.new(:title => 'foo')
|
9
|
+
@parent_record.associated_records << AssociatedRecord.new(:name => 'bar')
|
10
|
+
@parent_record.save
|
11
|
+
@parent_record.reload
|
12
|
+
@record = @parent_record.associated_records.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_any_transactions_are_open
|
16
|
+
Record.expects(:fetch_by_id).never
|
17
|
+
@record.transaction do
|
18
|
+
assert_equal @parent_record, @record.fetch_record
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_the_normal_association_is_loaded
|
23
|
+
# Warm the ActiveRecord association
|
24
|
+
@record.record
|
25
|
+
|
26
|
+
Record.expects(:fetch_by_id).never
|
27
|
+
assert_equal @parent_record, @record.fetch_record
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_fetching_the_association_should_fetch_the_record_from_identity_cache
|
31
|
+
Record.expects(:fetch_by_id).with(@parent_record.id).returns(@parent_record)
|
32
|
+
assert_equal @parent_record, @record.fetch_record
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_fetching_the_association_should_assign_the_result_to_the_association_so_that_successive_accesses_are_cached
|
36
|
+
Record.expects(:fetch_by_id).with(@parent_record.id).returns(@parent_record)
|
37
|
+
@record.fetch_record
|
38
|
+
assert @record.association(:record).loaded?
|
39
|
+
assert_equal @parent_record, @record.record
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_fetching_the_association_shouldnt_raise_if_the_record_cant_be_found
|
43
|
+
Record.expects(:fetch_by_id).with(@parent_record.id).returns(nil)
|
44
|
+
assert_equal nil, @record.fetch_record
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class NormalizedHasManyTest < IdentityCache::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
Record.cache_has_many :associated_records, :embed => false
|
7
|
+
|
8
|
+
@record = Record.new(:title => 'foo')
|
9
|
+
@record.not_cached_records << NotCachedRecord.new(:name => 'NoCache')
|
10
|
+
@record.associated_records << AssociatedRecord.new(:name => 'bar')
|
11
|
+
@record.associated_records << AssociatedRecord.new(:name => 'baz')
|
12
|
+
@record.save
|
13
|
+
@record.reload
|
14
|
+
@baz, @bar = @record.associated_records[0], @record.associated_records[1]
|
15
|
+
@not_cached = @record.not_cached_records.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_a_records_list_of_associated_ids_on_the_parent_record_retains_association_sort_order
|
19
|
+
assert_equal [2, 1], @record.associated_record_ids
|
20
|
+
|
21
|
+
AssociatedRecord.create(name: 'foo', record_id: @record.id)
|
22
|
+
@record.reload
|
23
|
+
assert_equal [3, 2, 1], @record.associated_record_ids
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_defining_a_denormalized_has_many_cache_caches_the_list_of_associated_ids_on_the_parent_record_during_cache_miss
|
27
|
+
fetched_record = Record.fetch(@record.id)
|
28
|
+
assert_equal [2, 1], fetched_record.cached_associated_record_ids
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_the_cached_the_list_of_associated_ids_on_the_parent_record_should_not_be_populated_by_default
|
32
|
+
assert_nil @record.cached_associated_record_ids
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_fetching_the_association_should_fetch_each_record_by_id
|
36
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_fetching_the_association_from_a_identity_cached_record_should_not_re_fetch_the_association_ids
|
40
|
+
@record = Record.fetch(@record.id)
|
41
|
+
@record.expects(:associated_record_ids).never
|
42
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_any_transaction_are_open
|
46
|
+
@record = Record.fetch(@record.id)
|
47
|
+
|
48
|
+
Record.expects(:fetch_multi).never
|
49
|
+
@record.transaction do
|
50
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_the_normal_association_is_loaded
|
55
|
+
# Warm the ActiveRecord association
|
56
|
+
@record.associated_records.to_a
|
57
|
+
|
58
|
+
Record.expects(:fetch_multi).never
|
59
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_saving_a_child_record_shouldnt_expire_the_parents_blob_if_the_foreign_key_hasnt_changed
|
63
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).never
|
64
|
+
@baz.name = 'foo'
|
65
|
+
@baz.save!
|
66
|
+
assert_equal [@baz.id, @bar.id], Record.fetch(@record.id).cached_associated_record_ids
|
67
|
+
assert_equal [@baz, @bar], Record.fetch(@record.id).fetch_associated_records
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_creating_a_child_record_should_expire_the_parents_cache_blob
|
71
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
72
|
+
@qux = @record.associated_records.create!(:name => 'qux')
|
73
|
+
assert_equal [@qux, @baz, @bar], Record.fetch(@record.id).fetch_associated_records
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_saving_a_child_record_should_expire_the_new_and_old_parents_cache_blob
|
77
|
+
@new_record = Record.create
|
78
|
+
@baz.record_id = @new_record.id
|
79
|
+
|
80
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
81
|
+
IdentityCache.cache.expects(:delete).with(@new_record.primary_cache_index_key).once
|
82
|
+
|
83
|
+
@baz.save!
|
84
|
+
|
85
|
+
assert_equal [@bar.id], Record.fetch(@record.id).cached_associated_record_ids
|
86
|
+
assert_equal [@bar], Record.fetch(@record.id).fetch_associated_records
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_saving_a_child_record_in_a_transaction_should_expire_the_new_and_old_parents_cache_blob
|
90
|
+
@new_record = Record.create
|
91
|
+
@baz.record_id = @new_record.id
|
92
|
+
|
93
|
+
@baz.transaction do
|
94
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
95
|
+
IdentityCache.cache.expects(:delete).with(@new_record.primary_cache_index_key).once
|
96
|
+
|
97
|
+
@baz.save!
|
98
|
+
@baz.reload
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal [@bar.id], Record.fetch(@record.id).cached_associated_record_ids
|
102
|
+
assert_equal [@bar], Record.fetch(@record.id).fetch_associated_records
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_destroying_a_child_record_should_expire_the_parents_cache_blob
|
106
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
107
|
+
@baz.destroy
|
108
|
+
assert_equal [@bar], @record.reload.fetch_associated_records
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_touching_a_child_record_should_expire_only_itself
|
112
|
+
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key).once
|
113
|
+
@baz.touch
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_touching_child_with_touch_true_on_parent_expires_parent
|
117
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
118
|
+
@not_cached.touch
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_saving_child_with_touch_true_on_parent_expires_parent
|
122
|
+
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
123
|
+
@not_cached.save
|
124
|
+
end
|
125
|
+
end
|