identity_cache 0.3.1 → 0.3.2
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 +4 -4
- data/.travis.yml +8 -2
- data/CHANGELOG.md +8 -0
- data/Gemfile.rails50 +6 -0
- data/README.md +10 -0
- data/dev.yml +54 -0
- data/identity_cache.gemspec +4 -4
- data/lib/identity_cache/belongs_to_caching.rb +5 -1
- data/lib/identity_cache/cache_key_generation.rb +1 -0
- data/lib/identity_cache/configuration_dsl.rb +13 -12
- data/lib/identity_cache/parent_model_expiration.rb +1 -1
- data/lib/identity_cache/query_api.rb +174 -82
- data/lib/identity_cache/version.rb +1 -1
- data/lib/identity_cache.rb +45 -1
- data/performance/cache_runner.rb +1 -1
- data/test/cache_hash_test.rb +0 -2
- data/test/cache_invalidation_test.rb +5 -4
- data/test/denormalized_has_many_test.rb +101 -7
- data/test/denormalized_has_one_test.rb +36 -0
- data/test/fetch_multi_test.rb +42 -0
- data/test/fetch_test.rb +32 -0
- data/test/helpers/database_connection.rb +4 -3
- data/test/index_cache_test.rb +2 -2
- data/test/memoized_attributes_test.rb +49 -0
- data/test/normalized_belongs_to_test.rb +24 -0
- data/test/normalized_has_many_test.rb +29 -5
- data/test/{prefetch_normalized_associations_test.rb → prefetch_associations_test.rb} +118 -9
- data/test/recursive_denormalized_has_many_test.rb +18 -2
- data/test/schema_change_test.rb +3 -2
- data/test/test_helper.rb +4 -2
- metadata +13 -12
- data/.ruby-version +0 -1
- data/Gemfile.rails40 +0 -6
- data/Gemfile.rails41 +0 -6
data/lib/identity_cache.rb
CHANGED
@@ -40,6 +40,25 @@ module IdentityCache
|
|
40
40
|
mattr_accessor :cache_namespace
|
41
41
|
self.cache_namespace = "IDC:#{CACHE_VERSION}:".freeze
|
42
42
|
|
43
|
+
version = Gem::Version.new(IdentityCache::VERSION)
|
44
|
+
|
45
|
+
# fetch_#{association} for a cache_has_many association returns a relation
|
46
|
+
# when fetch_returns_relation is set to true and an array when set to false.
|
47
|
+
mattr_accessor :fetch_returns_relation
|
48
|
+
self.fetch_returns_relation = version < Gem::Version.new("0.4")
|
49
|
+
|
50
|
+
# Inverse active record associations are set when loading embedded
|
51
|
+
# cache_has_many associations from the cache when never_set_inverse_association
|
52
|
+
# is false. When set to true, it will only set the inverse cached association.
|
53
|
+
mattr_accessor :never_set_inverse_association
|
54
|
+
self.never_set_inverse_association = version >= Gem::Version.new("0.4")
|
55
|
+
|
56
|
+
# Fetched records are not read-only and this could sometimes prevent IDC from
|
57
|
+
# reflecting what's truly in the database when fetch_read_only_records is false.
|
58
|
+
# When set to true, it will only return read-only records when cache is used.
|
59
|
+
mattr_accessor :fetch_read_only_records
|
60
|
+
self.fetch_read_only_records = version >= Gem::Version.new("0.4")
|
61
|
+
|
43
62
|
def included(base) #:nodoc:
|
44
63
|
raise AlreadyIncludedError if base.include?(IdentityCache::ConfigurationDSL)
|
45
64
|
|
@@ -58,7 +77,7 @@ module IdentityCache
|
|
58
77
|
# +cache_adaptor+ - A ActiveSupport::Cache::Store
|
59
78
|
#
|
60
79
|
def cache_backend=(cache_adaptor)
|
61
|
-
if @cache
|
80
|
+
if defined?(@cache)
|
62
81
|
cache.cache_backend = cache_adaptor
|
63
82
|
else
|
64
83
|
@cache = MemoizedCacheProxy.new(cache_adaptor)
|
@@ -131,6 +150,31 @@ module IdentityCache
|
|
131
150
|
result
|
132
151
|
end
|
133
152
|
|
153
|
+
def with_fetch_returns_relation(value = true)
|
154
|
+
previous_value = self.fetch_returns_relation
|
155
|
+
self.fetch_returns_relation = value
|
156
|
+
yield
|
157
|
+
ensure
|
158
|
+
self.fetch_returns_relation = previous_value
|
159
|
+
end
|
160
|
+
|
161
|
+
def with_never_set_inverse_association(value = true)
|
162
|
+
old_value = self.never_set_inverse_association
|
163
|
+
self.never_set_inverse_association = value
|
164
|
+
yield
|
165
|
+
ensure
|
166
|
+
self.never_set_inverse_association = old_value
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def with_fetch_read_only_records(value = true)
|
171
|
+
old_value = self.fetch_read_only_records
|
172
|
+
self.fetch_read_only_records = value
|
173
|
+
yield
|
174
|
+
ensure
|
175
|
+
self.fetch_read_only_records = old_value
|
176
|
+
end
|
177
|
+
|
134
178
|
private
|
135
179
|
|
136
180
|
def fetch_in_batches(keys)
|
data/performance/cache_runner.rb
CHANGED
@@ -14,7 +14,7 @@ require File.dirname(__FILE__) + '/../test/helpers/database_connection'
|
|
14
14
|
|
15
15
|
IdentityCache.logger = Logger.new(nil)
|
16
16
|
IdentityCache.cache_backend = ActiveSupport::Cache::MemcachedStore.new("localhost:#{$memcached_port}", :support_cas => true)
|
17
|
-
|
17
|
+
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
18
18
|
|
19
19
|
def create_record(id)
|
20
20
|
Item.new(id)
|
data/test/cache_hash_test.rb
CHANGED
@@ -10,6 +10,7 @@ class CacheInvalidationTest < IdentityCache::TestCase
|
|
10
10
|
@record.save
|
11
11
|
@record.reload
|
12
12
|
@baz, @bar = @record.associated_records[0], @record.associated_records[1]
|
13
|
+
@record.reload
|
13
14
|
end
|
14
15
|
|
15
16
|
def test_reload_invalidate_cached_ids
|
@@ -21,7 +22,7 @@ class CacheInvalidationTest < IdentityCache::TestCase
|
|
21
22
|
assert_equal [@baz.id, @bar.id], @record.instance_variable_get(variable_name)
|
22
23
|
|
23
24
|
@record.reload
|
24
|
-
assert_equal
|
25
|
+
assert_equal false, @record.instance_variable_defined?(variable_name)
|
25
26
|
|
26
27
|
@record.fetch_associated_record_ids
|
27
28
|
assert_equal [@baz.id, @bar.id], @record.instance_variable_get(variable_name)
|
@@ -36,7 +37,7 @@ class CacheInvalidationTest < IdentityCache::TestCase
|
|
36
37
|
assert_equal [@baz, @bar], @record.instance_variable_get(variable_name)
|
37
38
|
|
38
39
|
@record.reload
|
39
|
-
assert_equal
|
40
|
+
assert_equal false, @record.instance_variable_defined?(variable_name)
|
40
41
|
|
41
42
|
@record.fetch_associated_records
|
42
43
|
assert_equal [@baz, @bar], @record.instance_variable_get(variable_name)
|
@@ -45,14 +46,14 @@ class CacheInvalidationTest < IdentityCache::TestCase
|
|
45
46
|
def test_after_a_reload_the_cache_perform_as_expected
|
46
47
|
Item.cache_has_many :associated_records, :embed => :ids
|
47
48
|
|
48
|
-
assert_equal [@baz, @bar], @record.associated_records
|
49
49
|
assert_equal [@baz, @bar], @record.fetch_associated_records
|
50
|
+
assert_equal [@baz, @bar], @record.associated_records
|
50
51
|
|
51
52
|
@baz.destroy
|
52
53
|
@record.reload
|
53
54
|
|
54
|
-
assert_equal [@bar], @record.associated_records
|
55
55
|
assert_equal [@bar], @record.fetch_associated_records
|
56
|
+
assert_equal [@bar], @record.associated_records
|
56
57
|
end
|
57
58
|
|
58
59
|
def test_cache_invalidation_expire_properly_if_child_is_embed_in_multiple_parents
|
@@ -12,6 +12,14 @@ class DenormalizedHasManyTest < IdentityCache::TestCase
|
|
12
12
|
@record.reload
|
13
13
|
end
|
14
14
|
|
15
|
+
def test_uncached_record_from_the_db_should_come_back_with_association_array_when_fetch_returns_array
|
16
|
+
IdentityCache.with_fetch_returns_relation(false) do
|
17
|
+
assert_equal IdentityCache.fetch_returns_relation, false
|
18
|
+
record_from_db = Item.find(@record.id)
|
19
|
+
assert_equal Array, record_from_db.fetch_associated_records.class
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
15
23
|
def test_uncached_record_from_the_db_will_use_normal_association
|
16
24
|
expected = @record.associated_records
|
17
25
|
record_from_db = Item.find(@record.id)
|
@@ -22,6 +30,17 @@ class DenormalizedHasManyTest < IdentityCache::TestCase
|
|
22
30
|
assert_equal expected, record_from_db.fetch_associated_records
|
23
31
|
end
|
24
32
|
|
33
|
+
def test_on_cache_hit_record_should_come_back_with_cached_association_array_when_fetch_returns_array
|
34
|
+
IdentityCache.with_fetch_returns_relation(false) do
|
35
|
+
assert_equal IdentityCache.fetch_returns_relation, false
|
36
|
+
Item.fetch(@record.id) # warm cache
|
37
|
+
|
38
|
+
record_from_cache_hit = Item.fetch(@record.id)
|
39
|
+
assert_equal @record, record_from_cache_hit
|
40
|
+
assert_equal Array, record_from_cache_hit.fetch_associated_records.class
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
25
44
|
def test_on_cache_hit_record_should_come_back_with_cached_association
|
26
45
|
Item.fetch(@record.id) # warm cache
|
27
46
|
|
@@ -94,16 +113,91 @@ class DenormalizedHasManyTest < IdentityCache::TestCase
|
|
94
113
|
child.save!
|
95
114
|
end
|
96
115
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
116
|
+
def test_fetch_association_does_not_allow_chaining_when_fetch_returns_array
|
117
|
+
IdentityCache.with_fetch_returns_relation(false) do
|
118
|
+
check = proc { assert_equal false, Item.fetch(@record.id).fetch_associated_records.respond_to?(:where) }
|
119
|
+
2.times { check.call } # for miss and hit
|
120
|
+
Item.transaction { check.call }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_never_set_inverse_association_on_cache_hit
|
125
|
+
Item.fetch(@record.id) # warm cache
|
126
|
+
|
127
|
+
item = IdentityCache.with_never_set_inverse_association do
|
128
|
+
Item.fetch(@record.id)
|
101
129
|
end
|
130
|
+
|
131
|
+
associated_record = item.fetch_associated_records.to_a.first
|
132
|
+
assert item.object_id != associated_record.item.object_id
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_deprecated_set_inverse_association_on_cache_hit
|
136
|
+
Item.fetch(@record.id) # warm cache
|
137
|
+
|
138
|
+
item = Item.fetch(@record.id)
|
139
|
+
|
140
|
+
associated_record = item.fetch_associated_records.to_a.first
|
141
|
+
assert_equal item.object_id, associated_record.item.object_id
|
102
142
|
end
|
103
143
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
144
|
+
def test_returned_records_should_be_readonly_on_cache_hit
|
145
|
+
IdentityCache.with_fetch_read_only_records do
|
146
|
+
Item.fetch(@record.id) # warm cache
|
147
|
+
record_from_cache_hit = Item.fetch(@record.id)
|
148
|
+
assert record_from_cache_hit.fetch_associated_records.all?(&:readonly?)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_returned_records_should_be_readonly_on_cache_miss
|
153
|
+
IdentityCache.with_fetch_read_only_records do
|
154
|
+
record_from_cache_miss = Item.fetch(@record.id)
|
155
|
+
assert record_from_cache_miss.fetch_associated_records.all?(&:readonly?)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_db_returned_records_should_never_be_readonly
|
160
|
+
IdentityCache.with_fetch_read_only_records do
|
161
|
+
record_from_db = Item.find(@record.id)
|
162
|
+
uncached_records = record_from_db.associated_records
|
163
|
+
assert uncached_records.none?(&:readonly?)
|
164
|
+
assert record_from_db.fetch_associated_records.all?(&:readonly?)
|
165
|
+
assert record_from_db.associated_records.none?(&:readonly?)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_returned_records_with_open_transactions_should_not_be_readonly
|
170
|
+
IdentityCache.with_fetch_read_only_records do
|
171
|
+
Item.transaction do
|
172
|
+
assert_equal IdentityCache.should_use_cache?, false
|
173
|
+
assert Item.fetch(@record.id).fetch_associated_records.none?(&:readonly?)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class CheckAssociationTest < IdentityCache::TestCase
|
179
|
+
def test_unsupported_through_assocation
|
180
|
+
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
181
|
+
Item.has_many :deeply_through_associated_records, :through => :associated_records, foreign_key: 'associated_record_id', inverse_of: :item, :class_name => 'DeeplyAssociatedRecord'
|
182
|
+
Item.cache_has_many :deeply_through_associated_records, :embed => true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_unsupported_joins_in_assocation_scope
|
187
|
+
scope = -> { joins(:associated_record).where(associated_records: { name: 'contrived example' }) }
|
188
|
+
Item.has_many :deeply_joined_associated_records, scope, inverse_of: :item, class_name: 'DeeplyAssociatedRecord'
|
189
|
+
Item.cache_has_many :deeply_joined_associated_records, :embed => true
|
190
|
+
|
191
|
+
message = "caching association Item.deeply_joined_associated_records scoped with a join isn't supported"
|
192
|
+
assert_raises IdentityCache::UnsupportedAssociationError, message do
|
193
|
+
Item.fetch(1)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_cache_has_many_on_derived_model_raises
|
198
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
199
|
+
StiRecordTypeA.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
200
|
+
end
|
107
201
|
end
|
108
202
|
end
|
109
203
|
end
|
@@ -120,4 +120,40 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
120
120
|
StiRecordTypeA.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
def test_returned_record_should_be_readonly_on_cache_hit
|
125
|
+
IdentityCache.with_fetch_read_only_records do
|
126
|
+
Item.fetch_by_title('foo')
|
127
|
+
record_from_cache_hit = Item.fetch_by_title('foo')
|
128
|
+
assert record_from_cache_hit.fetch_associated.readonly?
|
129
|
+
refute record_from_cache_hit.associated.readonly?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_returned_record_should_be_readonly_on_cache_miss
|
134
|
+
IdentityCache.with_fetch_read_only_records do
|
135
|
+
assert IdentityCache.should_use_cache?
|
136
|
+
record_from_cache_miss = Item.fetch_by_title('foo')
|
137
|
+
assert record_from_cache_miss.fetch_associated.readonly?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_db_returned_record_should_never_be_readonly
|
142
|
+
IdentityCache.with_fetch_read_only_records do
|
143
|
+
record_from_db = Item.find_by_title('foo')
|
144
|
+
uncached_record = record_from_db.associated
|
145
|
+
refute uncached_record.readonly?
|
146
|
+
record_from_db.fetch_associated
|
147
|
+
refute uncached_record.readonly?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_returned_record_with_open_transactions_should_not_be_readonly
|
152
|
+
IdentityCache.with_fetch_read_only_records do
|
153
|
+
Item.transaction do
|
154
|
+
refute IdentityCache.should_use_cache?
|
155
|
+
refute Item.fetch_by_title('foo').fetch_associated.readonly?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
123
159
|
end
|
data/test/fetch_multi_test.rb
CHANGED
@@ -223,6 +223,48 @@ class FetchMultiTest < IdentityCache::TestCase
|
|
223
223
|
end
|
224
224
|
end
|
225
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
|
+
|
226
268
|
private
|
227
269
|
|
228
270
|
def populate_only_fred
|
data/test/fetch_test.rb
CHANGED
@@ -212,4 +212,36 @@ class FetchTest < IdentityCache::TestCase
|
|
212
212
|
StiRecordTypeA.fetch(1)
|
213
213
|
end
|
214
214
|
end
|
215
|
+
|
216
|
+
def test_returned_records_are_readonly_on_cache_hit
|
217
|
+
IdentityCache.with_fetch_read_only_records do
|
218
|
+
IdentityCache.cache.expects(:fetch).with(@blob_key).returns(@cached_value)
|
219
|
+
assert Item.fetch(1).readonly?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_returned_records_are_readonly_on_cache_miss
|
224
|
+
IdentityCache.with_fetch_read_only_records do
|
225
|
+
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
226
|
+
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
227
|
+
|
228
|
+
assert Item.exists_with_identity_cache?(1)
|
229
|
+
assert fetch.has_been_called_with?(@blob_key)
|
230
|
+
assert Item.fetch(1).readonly?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_returned_records_are_not_readonly_with_open_transactions
|
235
|
+
IdentityCache.with_fetch_read_only_records do
|
236
|
+
|
237
|
+
@record.transaction do
|
238
|
+
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
239
|
+
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
240
|
+
|
241
|
+
refute IdentityCache.should_use_cache?
|
242
|
+
refute fetch.has_been_called_with?(@blob_key)
|
243
|
+
refute Item.fetch(1).readonly?, "Fetched item was read-only"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
215
247
|
end
|
@@ -50,14 +50,15 @@ module DatabaseConnection
|
|
50
50
|
'mysql2' => {
|
51
51
|
'adapter' => 'mysql2',
|
52
52
|
'database' => 'identity_cache_test',
|
53
|
-
'host' => '127.0.0.1',
|
53
|
+
'host' => ENV['MYSQL_HOST'] || '127.0.0.1',
|
54
54
|
'username' => 'root'
|
55
55
|
},
|
56
56
|
'postgresql' => {
|
57
57
|
'adapter' => 'postgresql',
|
58
58
|
'database' => 'identity_cache_test',
|
59
|
-
'host' => '127.0.0.1',
|
60
|
-
'username' => 'postgres'
|
59
|
+
'host' => ENV['POSTGRES_HOST'] || '127.0.0.1',
|
60
|
+
'username' => 'postgres',
|
61
|
+
'prepared_statements' => false,
|
61
62
|
}
|
62
63
|
}
|
63
64
|
end
|
data/test/index_cache_test.rb
CHANGED
@@ -143,13 +143,13 @@ class IndexCacheTest < IdentityCache::TestCase
|
|
143
143
|
|
144
144
|
def test_cache_index_with_non_id_primary_key
|
145
145
|
KeyedRecord.cache_index :value
|
146
|
-
|
146
|
+
KeyedRecord.create!(value: "a") { |r| r.hashed_key = 123 }
|
147
147
|
assert_equal [123], KeyedRecord.fetch_by_value('a').map(&:id)
|
148
148
|
end
|
149
149
|
|
150
150
|
def test_unique_cache_index_with_non_id_primary_key
|
151
151
|
KeyedRecord.cache_index :value, unique: true
|
152
|
-
|
152
|
+
KeyedRecord.create!(value: "a") { |r| r.hashed_key = 123 }
|
153
153
|
assert_equal 123, KeyedRecord.fetch_by_value('a').id
|
154
154
|
end
|
155
155
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class MemoizedAttributesTest < IdentityCache::TestCase
|
4
|
+
def test_memoization_should_not_break_dirty_tracking_with_empty_cache
|
5
|
+
item = Item.create!
|
6
|
+
|
7
|
+
IdentityCache.cache.with_memoization do
|
8
|
+
Item.fetch(item.id).title = "my title"
|
9
|
+
Item.fetch(item.id).update!(title: "my title")
|
10
|
+
end
|
11
|
+
|
12
|
+
assert_equal "my title", Item.find(item.id).title
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_memoization_should_not_break_dirty_tracking_with_filled_cache
|
16
|
+
item = Item.create!
|
17
|
+
|
18
|
+
IdentityCache.cache.with_memoization do
|
19
|
+
Item.fetch(item.id)
|
20
|
+
Item.fetch(item.id).title = "my title"
|
21
|
+
Item.fetch(item.id).update!(title: "my title")
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_equal "my title", Item.find(item.id).title
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_memoization_with_fetch_multi_should_not_break_dirty_tracking_with_empty_cache
|
28
|
+
item = Item.create!
|
29
|
+
|
30
|
+
IdentityCache.cache.with_memoization do
|
31
|
+
Item.fetch_multi(item.id).first.title = "my title"
|
32
|
+
Item.fetch_multi(item.id).first.update!(title: "my title")
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal "my title", Item.find(item.id).title
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_memoization_with_fetch_multi_should_not_break_dirty_tracking_with_filled_cache
|
39
|
+
item = Item.create!
|
40
|
+
|
41
|
+
IdentityCache.cache.with_memoization do
|
42
|
+
Item.fetch_multi(item.id)
|
43
|
+
Item.fetch_multi(item.id).first.title = "my title"
|
44
|
+
Item.fetch_multi(item.id).first.update!(title: "my title")
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_equal "my title", Item.find(item.id).title
|
48
|
+
end
|
49
|
+
end
|
@@ -60,4 +60,28 @@ class NormalizedBelongsToTest < IdentityCache::TestCase
|
|
60
60
|
|
61
61
|
assert_equal @parent_record, PolymorphicRecord.first.fetch_owner
|
62
62
|
end
|
63
|
+
|
64
|
+
def test_returned_record_should_be_readonly_on_cache_hit
|
65
|
+
IdentityCache.with_fetch_read_only_records do
|
66
|
+
@record.fetch_item # warm cache
|
67
|
+
assert @record.fetch_item.readonly?
|
68
|
+
refute @record.item.readonly?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_returned_record_should_be_readonly_on_cache_miss
|
73
|
+
IdentityCache.with_fetch_read_only_records do
|
74
|
+
assert @record.fetch_item.readonly?
|
75
|
+
refute @record.item.readonly?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_returned_record_with_open_transactions_should_not_be_readonly
|
80
|
+
IdentityCache.with_fetch_read_only_records do
|
81
|
+
Item.transaction do
|
82
|
+
refute IdentityCache.should_use_cache?
|
83
|
+
refute @record.fetch_item.readonly?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
63
87
|
end
|
@@ -111,9 +111,10 @@ class NormalizedHasManyTest < IdentityCache::TestCase
|
|
111
111
|
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_any_transaction_are_open
|
112
112
|
@record = Item.fetch(@record.id)
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
114
|
+
assert_memcache_operations(0) do
|
115
|
+
@record.transaction do
|
116
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
117
|
+
end
|
117
118
|
end
|
118
119
|
end
|
119
120
|
|
@@ -121,8 +122,9 @@ class NormalizedHasManyTest < IdentityCache::TestCase
|
|
121
122
|
# Warm the ActiveRecord association
|
122
123
|
@record.associated_records.to_a
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
assert_memcache_operations(0) do
|
126
|
+
assert_equal [@baz, @bar], @record.fetch_associated_records
|
127
|
+
end
|
126
128
|
end
|
127
129
|
|
128
130
|
def test_saving_a_child_record_shouldnt_expire_the_parents_blob_if_the_foreign_key_hasnt_changed
|
@@ -195,4 +197,26 @@ class NormalizedHasManyTest < IdentityCache::TestCase
|
|
195
197
|
@not_cached.save!
|
196
198
|
end
|
197
199
|
|
200
|
+
def test_fetch_association_does_not_allow_chaining_when_fetch_returns_array
|
201
|
+
IdentityCache.with_fetch_returns_relation(false) do
|
202
|
+
check = proc { assert_equal false, Item.fetch(@record.id).fetch_associated_records.respond_to?(:where) }
|
203
|
+
2.times { check.call } # for miss and hit
|
204
|
+
Item.transaction { check.call }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_returned_records_should_be_readonly_on_cache_hit
|
209
|
+
IdentityCache.with_fetch_read_only_records do
|
210
|
+
Item.fetch(@record.id) # warm cache
|
211
|
+
record_from_cache_hit = Item.fetch(@record.id)
|
212
|
+
record_from_cache_hit.fetch_associated_records.all?(&:readonly?)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_returned_records_should_be_readonly_on_cache_miss
|
217
|
+
IdentityCache.with_fetch_read_only_records do
|
218
|
+
record_from_cache_miss = Item.fetch(@record.id)
|
219
|
+
assert record_from_cache_miss.fetch_associated_records.all?(&:readonly?)
|
220
|
+
end
|
221
|
+
end
|
198
222
|
end
|