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.
@@ -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)
@@ -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)
@@ -3,8 +3,6 @@ require 'test_helper'
3
3
  class CacheHashTest < IdentityCache::TestCase
4
4
 
5
5
  def test_memcache_hash
6
-
7
- prng = Random.new(Time.now.to_i)
8
6
  3.times do
9
7
  random_str = Array.new(200){rand(36).to_s(36)}.join
10
8
  hash_val = IdentityCache.memcache_hash(random_str)
@@ -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 nil, @record.instance_variable_get(variable_name)
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 nil, @record.instance_variable_get(variable_name)
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 test_unsupported_through_assocation
98
- assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
99
- Item.has_many :deeply_through_associated_records, :through => :associated_records, foreign_key: 'associated_record_id', inverse_of: :item, :class_name => 'DeeplyAssociatedRecord'
100
- Item.cache_has_many :deeply_through_associated_records, :embed => true
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 test_cache_has_many_on_derived_model_raises
105
- assert_raises(IdentityCache::DerivedModelError) do
106
- StiRecordTypeA.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
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
@@ -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
@@ -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
- fixture = KeyedRecord.create!(value: "a") { |r| r.hashed_key = 123 }
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
- fixture = KeyedRecord.create!(value: "a") { |r| r.hashed_key = 123 }
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
- Item.expects(:fetch_multi).never
115
- @record.transaction do
116
- assert_equal [@baz, @bar], @record.fetch_associated_records
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
- Item.expects(:fetch_multi).never
125
- assert_equal [@baz, @bar], @record.fetch_associated_records
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