identity_cache 0.2.5 → 0.3.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -4
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.rails40 +1 -0
  6. data/Gemfile.rails41 +1 -0
  7. data/Gemfile.rails42 +1 -0
  8. data/README.md +7 -0
  9. data/identity_cache.gemspec +1 -1
  10. data/lib/identity_cache.rb +5 -2
  11. data/lib/identity_cache/belongs_to_caching.rb +13 -5
  12. data/lib/identity_cache/cache_key_generation.rb +12 -19
  13. data/lib/identity_cache/configuration_dsl.rb +83 -84
  14. data/lib/identity_cache/parent_model_expiration.rb +4 -3
  15. data/lib/identity_cache/query_api.rb +93 -91
  16. data/lib/identity_cache/version.rb +2 -2
  17. data/test/attribute_cache_test.rb +42 -63
  18. data/test/deeply_nested_associated_record_test.rb +1 -0
  19. data/test/denormalized_has_many_test.rb +18 -0
  20. data/test/denormalized_has_one_test.rb +15 -5
  21. data/test/fetch_multi_test.rb +25 -3
  22. data/test/fetch_test.rb +20 -7
  23. data/test/fixtures/serialized_record.mysql2 +0 -0
  24. data/test/fixtures/serialized_record.postgresql +0 -0
  25. data/test/helpers/active_record_objects.rb +10 -0
  26. data/test/helpers/database_connection.rb +6 -1
  27. data/test/helpers/serialization_format.rb +1 -1
  28. data/test/index_cache_test.rb +50 -25
  29. data/test/normalized_belongs_to_test.rb +21 -6
  30. data/test/normalized_has_many_test.rb +44 -0
  31. data/test/{fetch_multi_with_batched_associations_test.rb → prefetch_normalized_associations_test.rb} +41 -3
  32. data/test/save_test.rb +14 -14
  33. data/test/schema_change_test.rb +2 -0
  34. data/test/test_helper.rb +4 -4
  35. metadata +11 -10
  36. data/Gemfile.rails32 +0 -5
  37. data/test/fixtures/serialized_record +0 -0
@@ -38,9 +38,10 @@ module IdentityCache
38
38
  old_parent = nil
39
39
  if transaction_changed_attributes[foreign_key].present?
40
40
  begin
41
- if parent_association.options[:polymorhpic]
42
- klass = transaction_changed_attributes[parent_association.association_foreign_key].try(:constantize)
43
- klass ||= new_parent.class
41
+ if parent_association.polymorphic?
42
+ parent_class_name = transaction_changed_attributes[parent_association.foreign_type]
43
+ parent_class_name ||= read_attribute(parent_association.foreign_type)
44
+ klass = parent_class_name.try!(:safe_constantize)
44
45
  else
45
46
  klass = parent_association.klass
46
47
  end
@@ -4,9 +4,6 @@ module IdentityCache
4
4
 
5
5
  included do |base|
6
6
  base.after_commit :expire_cache
7
- if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("4.0.4")
8
- base.after_touch :expire_cache
9
- end
10
7
  end
11
8
 
12
9
  module ClassMethods
@@ -19,11 +16,12 @@ module IdentityCache
19
16
 
20
17
  # Default fetcher added to the model on inclusion, it behaves like
21
18
  # ActiveRecord::Base.where(id: id).first
22
- def fetch_by_id(id)
23
- return unless id
19
+ def fetch_by_id(id, options={})
20
+ ensure_base_model
21
+ raise_if_scoped
24
22
  raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
25
- if IdentityCache.should_use_cache?
26
-
23
+ return unless id
24
+ record = if IdentityCache.should_use_cache?
27
25
  require_if_necessary do
28
26
  object = nil
29
27
  coder = IdentityCache.fetch(rails_cache_key(id)){ coder_from_record(object = resolve_cache_miss(id)) }
@@ -31,22 +29,25 @@ module IdentityCache
31
29
  IdentityCache.logger.error "[IDC id mismatch] fetch_by_id_requested=#{id} fetch_by_id_got=#{object.id} for #{object.inspect[(0..100)]} " if object && object.id != id.to_i
32
30
  object
33
31
  end
34
-
35
32
  else
36
- self.reorder(nil).where(primary_key => id).first
33
+ resolve_cache_miss(id)
37
34
  end
35
+ prefetch_associations(options[:includes], [record]) if record && options[:includes]
36
+ record
38
37
  end
39
38
 
40
39
  # Default fetcher added to the model on inclusion, it behaves like
41
40
  # ActiveRecord::Base.find, will raise ActiveRecord::RecordNotFound exception
42
41
  # if id is not in the cache or the db.
43
- def fetch(id)
44
- fetch_by_id(id) or raise(ActiveRecord::RecordNotFound, "Couldn't find #{self.name} with ID=#{id}")
42
+ def fetch(id, options={})
43
+ fetch_by_id(id, options) or raise(ActiveRecord::RecordNotFound, "Couldn't find #{self.name} with ID=#{id}")
45
44
  end
46
45
 
47
46
  # Default fetcher added to the model on inclusion, if behaves like
48
47
  # ActiveRecord::Base.find_all_by_id
49
48
  def fetch_multi(*ids)
49
+ ensure_base_model
50
+ raise_if_scoped
50
51
  raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
51
52
  options = ids.extract_options!
52
53
  ids.flatten!(1)
@@ -59,9 +60,7 @@ module IdentityCache
59
60
  coders_by_key = IdentityCache.fetch_multi(cache_keys) do |unresolved_keys|
60
61
  ids = unresolved_keys.map {|key| key_to_id_map[key] }
61
62
  records = find_batch(ids)
62
- found_records = records.compact
63
- found_records.each{ |record| record.send(:populate_association_caches) }
64
- key_to_record_map = found_records.index_by{ |record| rails_cache_key(record.id) }
63
+ key_to_record_map = records.compact.index_by{ |record| rails_cache_key(record.id) }
65
64
  records.map {|record| coder_from_record(record) }
66
65
  end
67
66
 
@@ -77,23 +76,19 @@ module IdentityCache
77
76
 
78
77
  private
79
78
 
79
+ def raise_if_scoped
80
+ if current_scope
81
+ raise UnsupportedScopeError, "IdentityCache doesn't support rails scopes"
82
+ end
83
+ end
84
+
80
85
  def record_from_coder(coder) #:nodoc:
81
- if coder.present? && coder.has_key?(:class)
82
- record = coder[:class].allocate
83
- empty_serialized_attrs =
84
- if defined?(ActiveRecord::Type::Serialized)
85
- coder[:class].columns.find { |t| t.cast_type.is_a?(ActiveRecord::Type::Serialized) }.nil?
86
- else
87
- coder[:class].serialized_attributes.empty?
88
- end
89
- unless empty_serialized_attrs
90
- coder = coder.dup
91
- coder['attributes'] = coder['attributes'].dup
92
- end
93
- record.init_with(coder)
86
+ if coder
87
+ klass = coder[:class]
88
+ record = klass.instantiate(coder[:attributes].dup)
94
89
 
95
90
  coder[:associations].each {|name, value| set_embedded_association(record, name, value) } if coder.has_key?(:associations)
96
- coder[:normalized_has_many].each {|name, ids| record.instance_variable_set(:"@#{record.class.cached_has_manys[name][:ids_variable_name]}", ids) } if coder.has_key?(:normalized_has_many)
91
+ coder[:association_ids].each {|name, ids| record.instance_variable_set(:"@#{record.class.cached_has_manys[name][:ids_variable_name]}", ids) } if coder.has_key?(:association_ids)
97
92
  record
98
93
  end
99
94
  end
@@ -114,10 +109,8 @@ module IdentityCache
114
109
  end
115
110
 
116
111
  def get_embedded_association(record, association, options) #:nodoc:
117
- embedded_variable = record.instance_variable_get(:"@#{options[:records_variable_name]}")
118
- if IdentityCache.unmap_cached_nil_for(embedded_variable).nil?
119
- nil
120
- elsif record.class.reflect_on_association(association).collection?
112
+ embedded_variable = record.public_send(options.fetch(:cached_accessor_name))
113
+ if record.class.reflect_on_association(association).collection?
121
114
  embedded_variable.map {|e| coder_from_record(e) }
122
115
  else
123
116
  coder_from_record(embedded_variable)
@@ -126,8 +119,10 @@ module IdentityCache
126
119
 
127
120
  def coder_from_record(record) #:nodoc:
128
121
  unless record.nil?
129
- coder = {:class => record.class }
130
- record.encode_with(coder)
122
+ coder = {
123
+ attributes: record.attributes_before_type_cast,
124
+ class: record.class,
125
+ }
131
126
  add_cached_associations_to_coder(record, coder)
132
127
  coder
133
128
  end
@@ -142,7 +137,7 @@ module IdentityCache
142
137
  end
143
138
  end
144
139
  if (cached_has_manys = klass.cached_has_manys).present?
145
- coder[:normalized_has_many] = cached_has_manys.each_with_object({}) do |(name, options), hash|
140
+ coder[:association_ids] = cached_has_manys.each_with_object({}) do |(name, options), hash|
146
141
  hash[name] = record.instance_variable_get(:"@#{options[:ids_variable_name]}") unless options[:embed] == true
147
142
  end
148
143
  end
@@ -168,9 +163,43 @@ module IdentityCache
168
163
  end
169
164
 
170
165
  def resolve_cache_miss(id)
171
- object = self.includes(cache_fetch_includes).reorder(nil).where(primary_key => id).try(:first)
172
- object.send(:populate_association_caches) if object
173
- object
166
+ record = self.includes(cache_fetch_includes).reorder(nil).where(primary_key => id).first
167
+ preload_id_embedded_associations([record]) if record
168
+ record
169
+ end
170
+
171
+ def preload_id_embedded_associations(records)
172
+ return if records.empty?
173
+ each_id_embedded_association do |options|
174
+ reflection = options.fetch(:association_reflection)
175
+ child_model = reflection.klass
176
+ scope = child_model.all
177
+ scope = scope.instance_exec(nil, &reflection.scope) if reflection.scope
178
+
179
+ pairs = scope.where(reflection.foreign_key => records.map(&:id)).pluck(reflection.foreign_key, reflection.active_record_primary_key)
180
+ ids_by_parent = Hash.new{ |hash, key| hash[key] = [] }
181
+ pairs.each do |parent_id, child_id|
182
+ ids_by_parent[parent_id] << child_id
183
+ end
184
+
185
+ records.each do |parent|
186
+ child_ids = ids_by_parent[parent.id]
187
+ parent.instance_variable_set(:"@#{options.fetch(:ids_variable_name)}", child_ids)
188
+ end
189
+ end
190
+ recursively_embedded_associations.each_value do |options|
191
+ child_model = options.fetch(:association_reflection).klass
192
+ if child_model.include?(IdentityCache)
193
+ child_records = records.flat_map(&options.fetch(:cached_accessor_name).to_sym).compact
194
+ child_model.send(:preload_id_embedded_associations, child_records)
195
+ end
196
+ end
197
+ end
198
+
199
+ def each_id_embedded_association
200
+ cached_has_manys.each_value do |options|
201
+ yield options if options.fetch(:embed) == :ids
202
+ end
174
203
  end
175
204
 
176
205
  def recursively_embedded_associations
@@ -212,6 +241,7 @@ module IdentityCache
212
241
  @id_column ||= columns.detect {|c| c.name == primary_key}
213
242
  ids = ids.map{ |id| connection.type_cast(id, @id_column) }
214
243
  records = where(primary_key => ids).includes(cache_fetch_includes).to_a
244
+ preload_id_embedded_associations(records)
215
245
  records_by_id = records.index_by(&:id)
216
246
  ids.map{ |id| records_by_id[id] }
217
247
  end
@@ -234,7 +264,7 @@ module IdentityCache
234
264
  end
235
265
 
236
266
  parent_record_to_child_records = Hash.new { |h, k| h[k] = [] }
237
- child_records = details[:association_class].fetch_multi(*ids_to_parent_record.keys)
267
+ child_records = details[:association_reflection].klass.fetch_multi(*ids_to_parent_record.keys)
238
268
  child_records.each do |child_record|
239
269
  parent_record = ids_to_parent_record[child_record.id]
240
270
  parent_record_to_child_records[parent_record] << child_record
@@ -251,11 +281,16 @@ module IdentityCache
251
281
  if details[:embed] == true
252
282
  raise ArgumentError.new("Embedded belongs_to associations do not support prefetching yet.")
253
283
  else
284
+ reflection = details[:association_reflection]
285
+ if reflection.polymorphic?
286
+ raise ArgumentError.new("Polymorphic belongs_to associations do not support prefetching yet.")
287
+ end
288
+
254
289
  ids_to_child_record = records.each_with_object({}) do |child_record, hash|
255
- parent_id = child_record.send(details[:foreign_key])
290
+ parent_id = child_record.send(reflection.foreign_key)
256
291
  hash[parent_id] = child_record if parent_id.present?
257
292
  end
258
- parent_records = details[:association_class].fetch_multi(ids_to_child_record.keys)
293
+ parent_records = reflection.klass.fetch_multi(ids_to_child_record.keys)
259
294
  parent_records.each do |parent_record|
260
295
  child_record = ids_to_child_record[parent_record.id]
261
296
  child_record.send(details[:prepopulate_method_name], parent_record)
@@ -277,8 +312,8 @@ module IdentityCache
277
312
  raise ArgumentError.new("Unknown cached association #{association} listed for prefetching")
278
313
  end
279
314
 
280
- if details && details[:association_class].respond_to?(:prefetch_associations, true)
281
- details[:association_class].send(:prefetch_associations, sub_associations, next_level_records)
315
+ if details && details[:association_reflection].klass.respond_to?(:prefetch_associations, true)
316
+ details[:association_reflection].klass.send(:prefetch_associations, sub_associations, next_level_records)
282
317
  end
283
318
  end
284
319
  end
@@ -306,39 +341,22 @@ module IdentityCache
306
341
 
307
342
  private
308
343
 
309
- def populate_association_caches # :nodoc:
310
- self.class.send(:embedded_associations).each do |cached_association, options|
311
- send(options[:population_method_name])
312
- reflection = options[:embed] == true && self.class.reflect_on_association(cached_association)
313
- if reflection && reflection.klass.respond_to?(:cached_has_manys)
314
- child_objects = Array.wrap(send(options[:cached_accessor_name]))
315
- child_objects.each{ |child| child.send(:populate_association_caches) }
316
- end
317
- end
318
- end
319
-
320
344
  def fetch_recursively_cached_association(ivar_name, association_name) # :nodoc:
321
- ivar_full_name = :"@#{ivar_name}"
322
345
  if IdentityCache.should_use_cache?
323
- populate_recursively_cached_association(ivar_name, association_name)
324
- assoc = IdentityCache.unmap_cached_nil_for(instance_variable_get(ivar_full_name))
346
+ ivar_full_name = :"@#{ivar_name}"
347
+
348
+ unless ivar_value = instance_variable_get(ivar_full_name)
349
+ ivar_value = IdentityCache.map_cached_nil_for(send(association_name))
350
+ instance_variable_set(ivar_full_name, ivar_value)
351
+ end
352
+
353
+ assoc = IdentityCache.unmap_cached_nil_for(ivar_value)
325
354
  assoc.is_a?(ActiveRecord::Associations::CollectionAssociation) ? assoc.reader : assoc
326
355
  else
327
356
  send(association_name.to_sym)
328
357
  end
329
358
  end
330
359
 
331
- def populate_recursively_cached_association(ivar_name, association_name) # :nodoc:
332
- ivar_full_name = :"@#{ivar_name}"
333
-
334
- value = instance_variable_get(ivar_full_name)
335
- return value unless value.nil?
336
-
337
- loaded_association = send(association_name)
338
-
339
- instance_variable_set(ivar_full_name, IdentityCache.map_cached_nil_for(loaded_association))
340
- end
341
-
342
360
  def expire_primary_index # :nodoc:
343
361
  return unless self.class.primary_cache_index_enabled
344
362
 
@@ -357,39 +375,23 @@ module IdentityCache
357
375
  IdentityCache.cache.delete(primary_cache_index_key)
358
376
  end
359
377
 
360
- def expire_secondary_indexes # :nodoc:
361
- return unless self.class.primary_cache_index_enabled
362
- cache_indexes.try(:each) do |fields|
363
- if self.destroyed?
364
- IdentityCache.cache.delete(secondary_cache_index_key_for_previous_values(fields))
365
- else
366
- new_cache_index_key = secondary_cache_index_key_for_current_values(fields)
367
- IdentityCache.cache.delete(new_cache_index_key)
368
-
369
- if !was_new_record?
370
- old_cache_index_key = secondary_cache_index_key_for_previous_values(fields)
371
- IdentityCache.cache.delete(old_cache_index_key) unless old_cache_index_key == new_cache_index_key
372
- end
373
- end
374
- end
375
- end
376
-
377
378
  def expire_attribute_indexes # :nodoc:
378
- cache_attributes.try(:each) do |(attribute, fields)|
379
+ cache_indexes.each do |(attribute, fields, unique)|
379
380
  unless was_new_record?
380
- old_cache_attribute_key = attribute_cache_key_for_attribute_and_previous_values(attribute, fields)
381
+ old_cache_attribute_key = attribute_cache_key_for_attribute_and_previous_values(attribute, fields, unique)
381
382
  IdentityCache.cache.delete(old_cache_attribute_key)
382
383
  end
383
- new_cache_attribute_key = attribute_cache_key_for_attribute_and_current_values(attribute, fields)
384
- if new_cache_attribute_key != old_cache_attribute_key
385
- IdentityCache.cache.delete(new_cache_attribute_key)
384
+ unless destroyed?
385
+ new_cache_attribute_key = attribute_cache_key_for_attribute_and_current_values(attribute, fields, unique)
386
+ if new_cache_attribute_key != old_cache_attribute_key
387
+ IdentityCache.cache.delete(new_cache_attribute_key)
388
+ end
386
389
  end
387
390
  end
388
391
  end
389
392
 
390
393
  def expire_cache # :nodoc:
391
394
  expire_primary_index
392
- expire_secondary_indexes
393
395
  expire_attribute_indexes
394
396
  true
395
397
  end
@@ -1,4 +1,4 @@
1
1
  module IdentityCache
2
- VERSION = "0.2.5"
3
- CACHE_VERSION = 5
2
+ VERSION = "0.3.0"
3
+ CACHE_VERSION = 6
4
4
  end
@@ -9,89 +9,80 @@ class AttributeCacheTest < IdentityCache::TestCase
9
9
 
10
10
  @parent = Item.create!(:title => 'bob')
11
11
  @record = @parent.associated_records.create!(:name => 'foo')
12
- @name_attribute_key = "#{NAMESPACE}attribute:AssociatedRecord:name:id:#{cache_hash(@record.id.to_s)}"
12
+ @name_attribute_key = "#{NAMESPACE}attr:AssociatedRecord:name:id:#{cache_hash(@record.id.to_s)}"
13
13
  IdentityCache.cache.clear
14
14
  end
15
15
 
16
- def test_attribute_values_are_returned_on_cache_hits
17
- IdentityCache.cache.expects(:fetch).with(@name_attribute_key).returns('foo')
18
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
19
- end
20
-
21
16
  def test_attribute_values_are_fetched_and_returned_on_cache_misses
22
17
  fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
23
- expects_fetch_associated_record_name_by_id(1, returns: 'foo')
24
18
 
25
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
19
+ assert_queries(1) do
20
+ assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
21
+ end
26
22
  assert fetch.has_been_called_with?(@name_attribute_key)
27
23
  end
28
24
 
29
- def test_attribute_values_are_stored_in_the_cache_on_cache_misses
30
- # Cache miss, so
31
- fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
32
-
33
- # Grab the value of the attribute from the DB
34
- expects_fetch_associated_record_name_by_id(1, returns: 'foo')
35
-
36
- # And write it back to the cache
37
- add = Spy.on(fetcher, :add).and_call_through
38
-
25
+ def test_attribute_values_are_returned_on_cache_hits
39
26
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
40
- assert fetch.has_been_called_with?(@name_attribute_key)
41
- assert add.has_been_called_with?(@name_attribute_key, 'foo')
42
- assert_equal 'foo', IdentityCache.cache.fetch(@name_attribute_key)
27
+
28
+ assert_queries(0) do
29
+ assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
30
+ end
43
31
  end
44
32
 
45
33
  def test_nil_is_stored_in_the_cache_on_cache_misses
46
- # Cache miss, so
47
- fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
48
-
49
- # Grab the value of the attribute from the DB
50
- expects_fetch_associated_record_name_by_id(1, returns: nil)
51
-
52
- # And write it back to the cache
53
- add = Spy.on(fetcher, :add).and_call_through
34
+ assert_equal nil, AssociatedRecord.fetch_name_by_id(2)
54
35
 
55
- assert_equal nil, AssociatedRecord.fetch_name_by_id(1)
56
- assert fetch.has_been_called_with?(@name_attribute_key)
57
- assert add.has_been_called_with?(@name_attribute_key, IdentityCache::CACHED_NIL)
36
+ assert_queries(0) do
37
+ assert_equal nil, AssociatedRecord.fetch_name_by_id(2)
38
+ end
58
39
  end
59
40
 
60
41
  def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_is_saved
61
- IdentityCache.cache.expects(:delete).with(@name_attribute_key)
62
- IdentityCache.cache.expects(:delete).with(blob_key_for_associated_record(1))
42
+ assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
43
+ assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
44
+
63
45
  @record.save!
46
+
47
+ assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
64
48
  end
65
49
 
66
50
  def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_with_changed_attributes_is_saved
67
- IdentityCache.cache.expects(:delete).with(@name_attribute_key)
68
- IdentityCache.cache.expects(:delete).with(blob_key_for_associated_record(1))
51
+ assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
52
+ assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
53
+
69
54
  @record.name = 'bar'
70
55
  @record.save!
56
+
57
+ assert_queries(1) { assert_equal 'bar', AssociatedRecord.fetch_name_by_id(1) }
71
58
  end
72
59
 
73
60
  def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_is_destroyed
74
- IdentityCache.cache.expects(:delete).with(@name_attribute_key)
75
- IdentityCache.cache.expects(:delete).with(blob_key_for_associated_record(1))
61
+ assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
62
+ assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
63
+
76
64
  @record.destroy
65
+
66
+ assert_queries(1) { assert_equal nil, AssociatedRecord.fetch_name_by_id(1) }
77
67
  end
78
68
 
79
69
  def test_cached_attribute_values_are_expired_from_the_cache_when_a_new_record_is_saved
80
- new_id = 2.to_s
81
- # primary index delete
82
- IdentityCache.cache.expects(:delete).with(blob_key_for_associated_record(new_id))
83
- # attribute cache delete
84
- IdentityCache.cache.expects(:delete).with("#{NAMESPACE}attribute:AssociatedRecord:name:id:#{cache_hash(new_id)}")
70
+ new_id = 2
71
+ assert_queries(1) { assert_equal nil, AssociatedRecord.fetch_name_by_id(new_id) }
72
+ assert_queries(0) { assert_equal nil, AssociatedRecord.fetch_name_by_id(new_id) }
73
+
85
74
  @parent.associated_records.create(:name => 'bar')
75
+
76
+ assert_queries(1) { assert_equal 'bar', AssociatedRecord.fetch_name_by_id(new_id) }
86
77
  end
87
78
 
88
79
  def test_fetching_by_attribute_delegates_to_block_if_transactions_are_open
89
- IdentityCache.cache.expects(:read).with(@name_attribute_key).never
90
-
91
- expects_fetch_associated_record_name_by_id(1, returns: 'foo')
80
+ IdentityCache.cache.expects(:read).never
92
81
 
93
82
  @record.transaction do
94
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
83
+ assert_queries(1) do
84
+ assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
85
+ end
95
86
  end
96
87
  end
97
88
 
@@ -101,21 +92,9 @@ class AttributeCacheTest < IdentityCache::TestCase
101
92
  assert_equal "Jim", AssociatedRecord.fetch_name_by_id(2)
102
93
  end
103
94
 
104
- private
105
-
106
- def blob_key_for_associated_record(id)
107
- cache_hash = cache_hash('id:integer,item_id:integer,item_two_id:integer,name:string')
108
- "#{NAMESPACE}blob:AssociatedRecord:#{cache_hash}:#{id}"
109
- end
110
-
111
- def quoted_table_column(model, column_name)
112
- "#{model.quoted_table_name}.#{model.connection.quote_column_name(column_name)}"
113
- end
114
-
115
- def expects_fetch_associated_record_name_by_id(id, options={})
116
- result = options[:returns] ? [options[:returns]] : []
117
- Item.connection.expects(:exec_query)
118
- .with(AssociatedRecord.unscoped.select(quoted_table_column(AssociatedRecord, :name)).where(id: id).limit(1).to_sql, any_parameters)
119
- .returns(ActiveRecord::Result.new(['name'], [result]))
95
+ def test_cache_attribute_on_derived_model_raises
96
+ assert_raises(IdentityCache::DerivedModelError) do
97
+ StiRecordTypeA.cache_attribute :name
98
+ end
120
99
  end
121
100
  end