identity_cache 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|