identity_cache 0.5.1 → 1.0.0
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.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +24 -9
- data/CHANGELOG.md +21 -0
- data/Gemfile +5 -1
- data/README.md +28 -26
- data/Rakefile +14 -5
- data/dev.yml +9 -16
- data/gemfiles/Gemfile.latest-release +6 -0
- data/gemfiles/Gemfile.rails-edge +6 -0
- data/gemfiles/Gemfile.rails52 +6 -0
- data/identity_cache.gemspec +26 -10
- data/lib/identity_cache.rb +49 -46
- data/lib/identity_cache/belongs_to_caching.rb +12 -40
- data/lib/identity_cache/cache_fetcher.rb +6 -5
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +4 -11
- data/lib/identity_cache/cache_key_generation.rb +17 -65
- data/lib/identity_cache/cache_key_loader.rb +128 -0
- data/lib/identity_cache/cached.rb +7 -0
- data/lib/identity_cache/cached/association.rb +87 -0
- data/lib/identity_cache/cached/attribute.rb +123 -0
- data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
- data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
- data/lib/identity_cache/cached/belongs_to.rb +93 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +51 -0
- data/lib/identity_cache/cached/primary_index.rb +97 -0
- data/lib/identity_cache/cached/recursive/association.rb +68 -0
- data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
- data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
- data/lib/identity_cache/cached/reference/association.rb +16 -0
- data/lib/identity_cache/cached/reference/has_many.rb +105 -0
- data/lib/identity_cache/cached/reference/has_one.rb +100 -0
- data/lib/identity_cache/configuration_dsl.rb +53 -215
- data/lib/identity_cache/encoder.rb +95 -0
- data/lib/identity_cache/expiry_hook.rb +36 -0
- data/lib/identity_cache/fallback_fetcher.rb +2 -1
- data/lib/identity_cache/load_strategy/eager.rb +28 -0
- data/lib/identity_cache/load_strategy/lazy.rb +71 -0
- data/lib/identity_cache/load_strategy/load_request.rb +20 -0
- data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
- data/lib/identity_cache/memoized_cache_proxy.rb +127 -58
- data/lib/identity_cache/parent_model_expiration.rb +45 -11
- data/lib/identity_cache/query_api.rb +128 -394
- data/lib/identity_cache/railtie.rb +8 -0
- data/lib/identity_cache/record_not_found.rb +6 -0
- data/lib/identity_cache/should_use_cache.rb +1 -0
- data/lib/identity_cache/version.rb +3 -2
- data/lib/identity_cache/with_primary_index.rb +136 -0
- data/lib/identity_cache/without_primary_index.rb +24 -3
- data/performance/cache_runner.rb +28 -34
- data/performance/cpu.rb +3 -2
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +44 -73
- data/Gemfile.rails42 +0 -6
- data/Gemfile.rails50 +0 -6
- data/test/attribute_cache_test.rb +0 -110
- data/test/cache_fetch_includes_test.rb +0 -46
- data/test/cache_hash_test.rb +0 -14
- data/test/cache_invalidation_test.rb +0 -139
- data/test/deeply_nested_associated_record_test.rb +0 -19
- data/test/denormalized_has_many_test.rb +0 -214
- data/test/denormalized_has_one_test.rb +0 -160
- data/test/fetch_multi_test.rb +0 -308
- data/test/fetch_test.rb +0 -258
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +0 -106
- data/test/helpers/database_connection.rb +0 -72
- data/test/helpers/serialization_format.rb +0 -51
- data/test/helpers/update_serialization_format.rb +0 -27
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -59
- data/test/memoized_cache_proxy_test.rb +0 -107
- data/test/normalized_belongs_to_test.rb +0 -107
- data/test/normalized_has_many_test.rb +0 -231
- data/test/normalized_has_one_test.rb +0 -9
- data/test/prefetch_associations_test.rb +0 -379
- data/test/readonly_test.rb +0 -109
- data/test/recursive_denormalized_has_many_test.rb +0 -131
- data/test/save_test.rb +0 -82
- data/test/schema_change_test.rb +0 -112
- data/test/serialization_format_change_test.rb +0 -16
- data/test/test_helper.rb +0 -140
@@ -1,160 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DenormalizedHasOneTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
7
|
-
Item.cache_has_one :associated
|
8
|
-
Item.cache_index :title, :unique => true
|
9
|
-
@record = Item.new(:title => 'foo')
|
10
|
-
@record.associated = AssociatedRecord.new(:name => 'bar')
|
11
|
-
@record.save
|
12
|
-
|
13
|
-
@record.reload
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_on_cache_miss_record_should_embed_associated_object
|
17
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
18
|
-
|
19
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
20
|
-
|
21
|
-
assert_equal @record, record_from_cache_miss
|
22
|
-
assert_not_nil @record.fetch_associated
|
23
|
-
assert_equal @record.associated, record_from_cache_miss.fetch_associated
|
24
|
-
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
25
|
-
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_on_cache_miss_record_should_embed_nil_object
|
29
|
-
|
30
|
-
@record.associated = nil
|
31
|
-
@record.save!
|
32
|
-
@record.reload
|
33
|
-
Item.expects(:resolve_cache_miss).with(@record.id).once.returns(@record)
|
34
|
-
|
35
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
36
|
-
|
37
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
38
|
-
record_from_cache_miss.expects(:associated).never
|
39
|
-
|
40
|
-
assert_equal @record, record_from_cache_miss
|
41
|
-
5.times do
|
42
|
-
assert_nil record_from_cache_miss.fetch_associated
|
43
|
-
end
|
44
|
-
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
45
|
-
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_on_record_from_the_db_will_use_normal_association
|
49
|
-
record_from_db = Item.find_by_title('foo')
|
50
|
-
|
51
|
-
assert_equal @record, record_from_db
|
52
|
-
assert_not_nil record_from_db.fetch_associated
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_on_cache_hit_record_should_come_back_with_cached_association
|
56
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
57
|
-
Item.fetch_by_title('foo')
|
58
|
-
|
59
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
60
|
-
expected = @record.associated
|
61
|
-
|
62
|
-
assert_equal @record, record_from_cache_hit
|
63
|
-
assert_equal expected, record_from_cache_hit.fetch_associated
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_on_cache_hit_record_should_come_back_with_cached_nil_association
|
67
|
-
@record.associated = nil
|
68
|
-
@record.save!
|
69
|
-
@record.reload
|
70
|
-
|
71
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
72
|
-
Item.fetch_by_title('foo')
|
73
|
-
|
74
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
75
|
-
record_from_cache_hit.expects(:associated).never
|
76
|
-
|
77
|
-
assert_equal @record, record_from_cache_hit
|
78
|
-
5.times do
|
79
|
-
assert_nil record_from_cache_hit.fetch_associated
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_changes_in_associated_record_should_expire_the_parents_cache
|
84
|
-
Item.fetch_by_title('foo')
|
85
|
-
key = @record.primary_cache_index_key
|
86
|
-
assert_not_nil IdentityCache.cache.fetch(key)
|
87
|
-
|
88
|
-
IdentityCache.cache.expects(:delete).at_least(1).with(key)
|
89
|
-
IdentityCache.cache.expects(:delete).with(@record.associated.primary_cache_index_key)
|
90
|
-
|
91
|
-
@record.associated.save
|
92
|
-
end
|
93
|
-
|
94
|
-
def test_cached_associations_after_commit_hook_will_not_fail_on_undefined_parent_association
|
95
|
-
ar = AssociatedRecord.new
|
96
|
-
ar.save
|
97
|
-
assert_nothing_raised { ar.expire_parent_caches }
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_cache_without_guessable_inverse_name_raises
|
101
|
-
assert_raises IdentityCache::InverseAssociationError do
|
102
|
-
Item.cache_has_one :polymorphic_record, :embed => true
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def test_cache_without_guessable_inverse_name_does_not_raise_when_inverse_name_specified
|
107
|
-
assert_nothing_raised do
|
108
|
-
Item.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def test_unsupported_through_assocation
|
113
|
-
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
114
|
-
Item.has_one :deeply_associated, :through => :associated, :class_name => 'DeeplyAssociatedRecord'
|
115
|
-
Item.cache_has_one :deeply_associated, :embed => true
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_cache_has_one_on_derived_model_raises
|
120
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
121
|
-
StiRecordTypeA.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_returned_record_should_be_readonly_on_cache_hit
|
126
|
-
IdentityCache.with_fetch_read_only_records do
|
127
|
-
Item.fetch_by_title('foo')
|
128
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
129
|
-
assert record_from_cache_hit.fetch_associated.readonly?
|
130
|
-
refute record_from_cache_hit.associated.readonly?
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def test_returned_record_should_be_readonly_on_cache_miss
|
135
|
-
IdentityCache.with_fetch_read_only_records do
|
136
|
-
assert IdentityCache.should_use_cache?
|
137
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
138
|
-
assert record_from_cache_miss.fetch_associated.readonly?
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_db_returned_record_should_never_be_readonly
|
143
|
-
IdentityCache.with_fetch_read_only_records do
|
144
|
-
record_from_db = Item.find_by_title('foo')
|
145
|
-
uncached_record = record_from_db.associated
|
146
|
-
refute uncached_record.readonly?
|
147
|
-
record_from_db.fetch_associated
|
148
|
-
refute uncached_record.readonly?
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def test_returned_record_with_open_transactions_should_not_be_readonly
|
153
|
-
IdentityCache.with_fetch_read_only_records do
|
154
|
-
Item.transaction do
|
155
|
-
refute IdentityCache.should_use_cache?
|
156
|
-
refute Item.fetch_by_title('foo').fetch_associated.readonly?
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
data/test/fetch_multi_test.rb
DELETED
@@ -1,308 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class FetchMultiTest < IdentityCache::TestCase
|
4
|
-
NAMESPACE = IdentityCache.cache_namespace
|
5
|
-
|
6
|
-
def setup
|
7
|
-
super
|
8
|
-
@bob = Item.create!(:title => 'bob')
|
9
|
-
@joe = Item.create!(:title => 'joe')
|
10
|
-
@fred = Item.create!(:title => 'fred')
|
11
|
-
@bob_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
12
|
-
@joe_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:2"
|
13
|
-
@fred_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:3"
|
14
|
-
@tenth_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:10"
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_fetch_multi_with_no_records
|
18
|
-
assert_equal [], Item.fetch_multi
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_fetch_multi_namespace
|
22
|
-
Item.send(:include, SwitchNamespace)
|
23
|
-
bob_blob_key, joe_blob_key, fred_blob_key = [@bob_blob_key, @joe_blob_key, @fred_blob_key].map { |k| "ns:#{k}" }
|
24
|
-
cache_response = {}
|
25
|
-
cache_response[bob_blob_key] = cache_response_for(@bob)
|
26
|
-
cache_response[joe_blob_key] = cache_response_for(@joe)
|
27
|
-
cache_response[fred_blob_key] = cache_response_for(@fred)
|
28
|
-
IdentityCache.cache.expects(:fetch_multi).with(bob_blob_key, joe_blob_key, fred_blob_key).returns(cache_response)
|
29
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_fetch_multi_with_all_hits
|
33
|
-
cache_response = {}
|
34
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
35
|
-
cache_response[@joe_blob_key] = cache_response_for(@joe)
|
36
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
37
|
-
IdentityCache.cache.expects(:fetch_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
|
38
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_fetch_multi_with_all_misses
|
42
|
-
cache_response = {}
|
43
|
-
cache_response[@bob_blob_key] = nil
|
44
|
-
cache_response[@joe_blob_key] = nil
|
45
|
-
cache_response[@fred_blob_key] = nil
|
46
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
47
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
48
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_fetch_multi_with_mixed_hits_and_misses
|
52
|
-
cache_response = {}
|
53
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
54
|
-
cache_response[@joe_blob_key] = nil
|
55
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
56
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
57
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
58
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_responses_in_the_wrong_order
|
62
|
-
cache_response = {}
|
63
|
-
cache_response[@bob_blob_key] = nil
|
64
|
-
cache_response[@joe_blob_key] = nil
|
65
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
66
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
67
|
-
assert_equal [@bob, @fred, @joe], Item.fetch_multi(@bob.id, @fred.id, @joe.id)
|
68
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @fred_blob_key, @joe_blob_key)
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_non_existant_keys_1
|
72
|
-
populate_only_fred
|
73
|
-
|
74
|
-
fetch_multi = fetch_multi_stub(@cache_response)
|
75
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(10, @bob.id, @joe.id, @fred.id)
|
76
|
-
assert fetch_multi.has_been_called_with?(@tenth_blob_key, @bob_blob_key, @joe_blob_key, @fred_blob_key)
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_non_existant_keys_2
|
80
|
-
populate_only_fred
|
81
|
-
|
82
|
-
fetch_multi = fetch_multi_stub(@cache_response)
|
83
|
-
assert_equal [@fred, @bob, @joe], Item.fetch_multi(@fred.id, @bob.id, 10, @joe.id)
|
84
|
-
assert fetch_multi.has_been_called_with?(@fred_blob_key, @bob_blob_key, @tenth_blob_key, @joe_blob_key)
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
def test_fetch_multi_works_with_nils
|
89
|
-
cache_result = {1 => IdentityCache::CACHED_NIL, 2 => IdentityCache::CACHED_NIL}
|
90
|
-
fetch_result = {1 => nil, 2 => nil}
|
91
|
-
|
92
|
-
fetcher.expects(:cas_multi).with([1, 2]).twice.returns(nil, cache_result)
|
93
|
-
fetcher.expects(:add).with(1, IdentityCache::CACHED_NIL).once
|
94
|
-
fetcher.expects(:add).with(2, IdentityCache::CACHED_NIL).once
|
95
|
-
|
96
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
97
|
-
[nil, nil]
|
98
|
-
end
|
99
|
-
assert_equal fetch_result, results
|
100
|
-
|
101
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
102
|
-
flunk "Contents should have been fetched from cache successfully"
|
103
|
-
end
|
104
|
-
|
105
|
-
assert_equal fetch_result, results
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_fetch_multi_works_with_blanks
|
109
|
-
cache_result = {1 => false, 2 => ' '}
|
110
|
-
|
111
|
-
fetcher.expects(:fetch_multi).with([1,2]).returns(cache_result)
|
112
|
-
|
113
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
114
|
-
flunk "Contents should have been fetched from cache successfully"
|
115
|
-
end
|
116
|
-
|
117
|
-
assert_equal cache_result, results
|
118
|
-
end
|
119
|
-
|
120
|
-
def test_fetch_multi_duplicate_ids
|
121
|
-
assert_equal [@joe, @bob, @joe], Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
122
|
-
end
|
123
|
-
|
124
|
-
def test_fetch_multi_with_duplicate_ids_hits_backend_once_per_id
|
125
|
-
cache_response = {
|
126
|
-
@joe_blob_key => cache_response_for(@joe),
|
127
|
-
@bob_blob_key => cache_response_for(@bob),
|
128
|
-
}
|
129
|
-
|
130
|
-
fetcher.expects(:fetch_multi).with([@joe_blob_key, @bob_blob_key]).returns(cache_response)
|
131
|
-
result = Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
132
|
-
|
133
|
-
assert_equal [@joe, @bob, @joe], result
|
134
|
-
end
|
135
|
-
|
136
|
-
def test_fetch_multi_with_open_transactions_hits_the_database
|
137
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
138
|
-
fetcher.expects(:fetch_multi).never
|
139
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_fetch_multi_with_open_transactions_returns_results_in_the_order_of_the_passed_ids
|
143
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
144
|
-
assert_equal [@joe, @bob, @fred], Item.fetch_multi(@joe.id, @bob.id, @fred.id)
|
145
|
-
end
|
146
|
-
|
147
|
-
def test_fetch_multi_with_open_transactions_should_compacts_returned_array
|
148
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
149
|
-
assert_equal [@joe, @fred], Item.fetch_multi(@joe.id, 0, @fred.id)
|
150
|
-
end
|
151
|
-
|
152
|
-
def test_fetch_multi_with_duplicate_ids_in_transaction_returns_results_in_the_order_of_the_passed_ids
|
153
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
154
|
-
assert_equal [@joe, @bob, @joe], Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
155
|
-
end
|
156
|
-
|
157
|
-
def test_find_batch_coerces_ids_to_primary_key_type
|
158
|
-
mock_relation = mock("ActiveRecord::Relation")
|
159
|
-
Item.expects(:where).returns(mock_relation)
|
160
|
-
mock_relation.expects(:includes).returns(stub(:to_a => [@bob, @joe, @fred]))
|
161
|
-
|
162
|
-
Item.send(:find_batch, [@bob, @joe, @fred].map(&:id).map(&:to_s))
|
163
|
-
end
|
164
|
-
|
165
|
-
def test_fetch_multi_doesnt_freeze_keys
|
166
|
-
cache_response = {}
|
167
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
168
|
-
cache_response[@joe_blob_key] = cache_response_for(@fred)
|
169
|
-
|
170
|
-
IdentityCache.expects(:fetch_multi).with{ |*args| args.none?(&:frozen?) }.returns(cache_response)
|
171
|
-
|
172
|
-
Item.fetch_multi(@bob.id, @joe.id)
|
173
|
-
end
|
174
|
-
|
175
|
-
def test_fetch_multi_array
|
176
|
-
assert_equal [@joe, @bob], Item.fetch_multi([@joe.id, @bob.id])
|
177
|
-
end
|
178
|
-
|
179
|
-
def test_fetch_multi_reads_in_batches
|
180
|
-
cache_response = {}
|
181
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
182
|
-
cache_response[@joe_blob_key] = cache_response_for(@joe)
|
183
|
-
|
184
|
-
with_batch_size 1 do
|
185
|
-
fetcher.expects(:fetch_multi).with([@bob_blob_key]).returns(cache_response).once
|
186
|
-
fetcher.expects(:fetch_multi).with([@joe_blob_key]).returns(cache_response).once
|
187
|
-
assert_equal [@bob, @joe], Item.fetch_multi(@bob.id, @joe.id)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def test_fetch_multi_max_stack_level
|
192
|
-
cache_response = { @fred_blob_key => cache_response_for(@fred) }
|
193
|
-
fetcher.stubs(:fetch_multi).returns(cache_response)
|
194
|
-
assert_nothing_raised { Item.fetch_multi([@fred.id] * 200_000) }
|
195
|
-
end
|
196
|
-
|
197
|
-
def test_fetch_multi_with_non_id_primary_key
|
198
|
-
fixture = KeyedRecord.create!(:value => "a") { |r| r.hashed_key = 123 }
|
199
|
-
assert_equal [fixture], KeyedRecord.fetch_multi(123, 456)
|
200
|
-
end
|
201
|
-
|
202
|
-
def test_fetch_multi_after_expiring_a_record
|
203
|
-
Item.fetch_multi(@joe.id, @fred.id)
|
204
|
-
@bob.send(:expire_cache)
|
205
|
-
assert_equal IdentityCache::DELETED, backend.read(@bob.primary_cache_index_key)
|
206
|
-
|
207
|
-
add = Spy.on(IdentityCache.cache.cache_fetcher, :add).and_call_through
|
208
|
-
|
209
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
210
|
-
refute add.has_been_called?
|
211
|
-
assert_equal cache_response_for(Item.find(@bob.id)), backend.read(@bob.primary_cache_index_key)
|
212
|
-
end
|
213
|
-
|
214
|
-
def test_fetch_multi_raises_when_called_on_a_scope
|
215
|
-
assert_raises(IdentityCache::UnsupportedScopeError) do
|
216
|
-
Item.where(updated_at: nil).fetch_multi(@bob.id, @joe.id, @fred.id)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def test_fetch_multi_on_derived_model_raises
|
221
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
222
|
-
StiRecordTypeA.fetch_multi(1, 2)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def test_fetch_multi_with_mixed_hits_and_misses_returns_only_readonly_records
|
227
|
-
IdentityCache.with_fetch_read_only_records do
|
228
|
-
cache_response = {}
|
229
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
230
|
-
cache_response[@joe_blob_key] = nil
|
231
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
232
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
233
|
-
|
234
|
-
response = Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
235
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
236
|
-
assert_equal [@bob, @joe, @fred], response
|
237
|
-
|
238
|
-
assert response.all?(&:readonly?)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_responses_in_the_wrong_order_returns_readonly
|
243
|
-
IdentityCache.with_fetch_read_only_records do
|
244
|
-
cache_response = {}
|
245
|
-
cache_response[@bob_blob_key] = nil
|
246
|
-
cache_response[@joe_blob_key] = nil
|
247
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
248
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
249
|
-
|
250
|
-
response = Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
251
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
252
|
-
assert_equal [@bob, @joe, @fred], response
|
253
|
-
|
254
|
-
assert response.all?(&:readonly?)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def test_fetch_multi_with_open_transactions_returns_non_readonly_records
|
259
|
-
IdentityCache.with_fetch_read_only_records do
|
260
|
-
Item.transaction do
|
261
|
-
assert_equal IdentityCache.should_use_cache?, false
|
262
|
-
IdentityCache.cache.expects(:fetch_multi).never
|
263
|
-
assert Item.fetch_multi(@bob.id, @joe.id, @fred.id).none?(&:readonly?)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def test_fetch_multi_with_no_keys_does_not_query_when_cache_is_disabled
|
269
|
-
Item.stubs(:should_use_cache?).returns(false)
|
270
|
-
|
271
|
-
assert_queries(0) do
|
272
|
-
assert_memcache_operations(0) do
|
273
|
-
Item.fetch_multi
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
private
|
279
|
-
|
280
|
-
def populate_only_fred
|
281
|
-
@cache_response = {}
|
282
|
-
@cache_response[@bob_blob_key] = nil
|
283
|
-
@cache_response[@joe_blob_key] = nil
|
284
|
-
@cache_response[@tenth_blob_key] = nil
|
285
|
-
@cache_response[@fred_blob_key] = cache_response_for(@fred)
|
286
|
-
end
|
287
|
-
|
288
|
-
def cache_response_for(record)
|
289
|
-
{ class: record.class.name, attributes: record.attributes_before_type_cast }
|
290
|
-
end
|
291
|
-
|
292
|
-
def with_batch_size(size)
|
293
|
-
previous_batch_size = IdentityCache::BATCH_SIZE
|
294
|
-
IdentityCache.send(:remove_const, :BATCH_SIZE)
|
295
|
-
IdentityCache.const_set(:BATCH_SIZE, size)
|
296
|
-
yield
|
297
|
-
ensure
|
298
|
-
IdentityCache.send(:remove_const, :BATCH_SIZE)
|
299
|
-
IdentityCache.const_set(:BATCH_SIZE, previous_batch_size)
|
300
|
-
end
|
301
|
-
|
302
|
-
def fetch_multi_stub(cache_response)
|
303
|
-
Spy.on(IdentityCache.cache, :fetch_multi).and_return do |*args, &block|
|
304
|
-
nil_keys = cache_response.select {|_, v| v.nil? }.keys
|
305
|
-
cache_response.merge(Hash[nil_keys.zip(block.call(nil_keys))])
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|