identity_cache 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YmQyZjllMTVlOTNiMmUxMDkxMzJlNzYzYjY0ODZlMmQ4Y2E0YTNkNA==
5
- data.tar.gz: !binary |-
6
- OGQ2MDc5Zjk2NmY3MTcwMmIxNDkzYjczNTZjMmJhNjMwMWI1ZjJjYg==
2
+ SHA1:
3
+ metadata.gz: 2692fa11451bb0243fcb3450d6f6c2550110afea
4
+ data.tar.gz: dbd4fa2555037a5ddd90e36f2aea9ae03fff6907
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MmM4NmJmMTY2MDRkNDdkYzRjMDg3NTQ4OTRlMzgyOWJiYzY4Y2RjMDQxZDQ0
10
- NDA2NzYwZGIyN2UzMjVkNGU1MTI1ZTViNDU4YThkNTAxNGYyY2UxMWU5Y2U4
11
- MzhjN2JjMDkyMWVmYjhkMWRmMmIyMmVmZjI4NzJkYTExZGZiYmM=
12
- data.tar.gz: !binary |-
13
- Yjg4MTdkOGI0OTJmY2ZkMWEwMDY0NjViMDE2MjdiMDg4NTkzZTc0MmU1YjEy
14
- NzI1Nzc0ZjQ0ODk1YWY0M2ExMDdjNWNjMjQ5OGY3Y2YyMTcyNzQ3MjA3OTY3
15
- YWMxMTlkZTgxNjQ0OTgyYmY3YzY5OWYzY2QyY2NlZjZkNzUwYTQ=
6
+ metadata.gz: 61e4b2e382f4a3706a5851ab2d8268bac77941a318b1ef81ad4babc4439b45a3e6c361a123e8d0bfeec2e136f8b0b8815825d1163a5d2a4c28f92c2bb11024d1
7
+ data.tar.gz: 830d6ef767b8b62167bd7f016966d56f6c1d1bf99c501c75293d4731cd61f58683396f622a654190c39e91ab7fd5e6b6cdad5051ead943306ffff27ad86399c1
data/.gitignore CHANGED
@@ -3,7 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
6
+ *.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.0
data/.travis.yml CHANGED
@@ -2,12 +2,16 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - jruby-19mode
5
+ - 2.1.0
6
+ - jruby
7
+ gemfile:
8
+ - Gemfile
9
+ - Gemfile.rails4
6
10
  services:
7
11
  - memcache
8
12
  - mysql
9
13
  matrix:
10
14
  allow_failures:
11
- - rvm: jruby-19mode
15
+ - rvm: jruby
12
16
  before_script:
13
17
  - mysql -e 'create database identity_cache_test'
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in identity_cache.gemspec
4
4
  gemspec
5
+
6
+ gem 'activerecord', '~> 3.2.16'
7
+ gem 'activesupport', '~> 3.2.16'
data/Gemfile.rails4 ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in identity_cache.gemspec
4
+ gemspec
5
+
6
+ gem 'activerecord', '~> 4.0.1'
7
+ gem 'activesupport', '~> 4.0.1'
8
+ gem 'dalli'
data/README.md CHANGED
@@ -244,4 +244,5 @@ Harry Brundage (@hornairs)
244
244
  Dylan Smith (@dylanahsmith)
245
245
  Tobias Lütke (@tobi)
246
246
  John Duff (@jduff)
247
- Francis Bogsany (@fbogsany)
247
+ Francis Bogsany (@fbogsany)
248
+ Arthur Neves (@arthurnn)
@@ -15,10 +15,8 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = IdentityCache::VERSION
17
17
 
18
-
19
- gem.add_dependency('ar_transaction_changes', '0.0.1')
20
- gem.add_dependency('activerecord', '~> 3.2.12')
21
- gem.add_dependency('activesupport', '~> 3.2.12')
18
+ gem.add_dependency('ar_transaction_changes', '= 0.0.3')
19
+ gem.add_dependency('activerecord', '>= 3.2', '< 4.1')
22
20
 
23
21
  gem.add_development_dependency('memcache-client')
24
22
  gem.add_development_dependency('rake')
@@ -29,8 +27,6 @@ Gem::Specification.new do |gem|
29
27
  gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
30
28
  gem.add_development_dependency 'jdbc-mysql'
31
29
  else
32
- gem.add_development_dependency('debugger')
33
- gem.add_development_dependency('ruby-prof')
34
30
  gem.add_development_dependency('cityhash', '0.6.0')
35
31
  gem.add_development_dependency('mysql2')
36
32
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_record'
2
+ require 'active_support/core_ext/module/attribute_accessors'
2
3
  require 'ar_transaction_changes'
3
4
 
4
5
  require "identity_cache/version"
@@ -26,6 +27,9 @@ module IdentityCache
26
27
  attr_accessor :readonly
27
28
  attr_writer :logger
28
29
 
30
+ mattr_accessor :cache_namespace
31
+ self.cache_namespace = "IDC:#{CACHE_VERSION}:".freeze
32
+
29
33
  def included(base) #:nodoc:
30
34
  raise AlreadyIncludedError if base.respond_to? :cache_indexes
31
35
 
@@ -43,7 +47,11 @@ module IdentityCache
43
47
  # +cache_adaptor+ - A ActiveSupport::Cache::Store
44
48
  #
45
49
  def cache_backend=(cache_adaptor)
46
- cache.cache_backend = cache_adaptor
50
+ if @cache
51
+ cache.cache_backend = cache_adaptor
52
+ else
53
+ @cache = MemoizedCacheProxy.new(cache_adaptor)
54
+ end
47
55
  end
48
56
 
49
57
  def cache
@@ -24,7 +24,7 @@ module IdentityCache
24
24
  end
25
25
 
26
26
  def build_normalized_belongs_to_cache(association, options)
27
- self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
27
+ self.class_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
28
28
  def #{options[:cached_accessor_name]}
29
29
  if IdentityCache.should_cache? && #{options[:foreign_key]}.present? && !association(:#{association}).loaded?
30
30
  self.#{association} = #{options[:association_class]}.fetch_by_id(#{options[:foreign_key]})
@@ -19,8 +19,8 @@ module IdentityCache
19
19
  end
20
20
 
21
21
  def self.embedded_associations(klass)
22
- if klass.respond_to?(:all_cached_associations_needing_population)
23
- klass.all_cached_associations_needing_population
22
+ if klass.respond_to?(:all_embedded_associations)
23
+ klass.all_embedded_associations
24
24
  else
25
25
  {}
26
26
  end
@@ -45,7 +45,8 @@ module IdentityCache
45
45
  end
46
46
 
47
47
  def rails_cache_key_namespace
48
- DEFAULT_NAMESPACE
48
+ ns = IdentityCache.cache_namespace
49
+ ns.is_a?(Proc) ? ns.call(self) : ns
49
50
  end
50
51
 
51
52
  private
@@ -59,17 +60,25 @@ module IdentityCache
59
60
  end
60
61
 
61
62
  def secondary_cache_index_key_for_current_values(fields) # :nodoc:
62
- self.class.rails_cache_index_key_for_fields_and_values(fields, fields.collect {|field| self.send(field)})
63
+ self.class.rails_cache_index_key_for_fields_and_values(fields, current_values_for_fields(fields))
63
64
  end
64
65
 
65
66
  def secondary_cache_index_key_for_previous_values(fields) # :nodoc:
66
67
  self.class.rails_cache_index_key_for_fields_and_values(fields, old_values_for_fields(fields))
67
68
  end
68
69
 
70
+ def attribute_cache_key_for_attribute_and_current_values(attribute, fields) # :nodoc:
71
+ self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, current_values_for_fields(fields))
72
+ end
73
+
69
74
  def attribute_cache_key_for_attribute_and_previous_values(attribute, fields) # :nodoc:
70
75
  self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, old_values_for_fields(fields))
71
76
  end
72
77
 
78
+ def current_values_for_fields(fields) # :nodoc:
79
+ fields.collect {|field| self.send(field)}
80
+ end
81
+
73
82
  def old_values_for_fields(fields) # :nodoc:
74
83
  fields.map do |field|
75
84
  field_string = field.to_s
@@ -52,7 +52,7 @@ module IdentityCache
52
52
  arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
53
53
 
54
54
  if options[:unique]
55
- self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
55
+ self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
56
56
  def fetch_by_#{field_list}(#{arg_list})
57
57
  identity_cache_single_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}])
58
58
  end
@@ -63,7 +63,7 @@ module IdentityCache
63
63
  end
64
64
  CODE
65
65
  else
66
- self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
66
+ self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__ + 1)
67
67
  def fetch_by_#{field_list}(#{arg_list})
68
68
  identity_cache_multiple_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}])
69
69
  end
@@ -170,7 +170,7 @@ module IdentityCache
170
170
  field_list = fields.join("_and_")
171
171
  arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
172
172
 
173
- self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
173
+ self.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
174
174
  def fetch_#{attribute}_by_#{field_list}(#{arg_list})
175
175
  attribute_dynamic_fetcher(#{attribute.inspect}, #{fields.inspect}, [#{arg_list}])
176
176
  end
@@ -210,7 +210,7 @@ module IdentityCache
210
210
 
211
211
 
212
212
  unless instance_methods.include?(options[:cached_accessor_name].to_sym)
213
- self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
213
+ self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
214
214
  def #{options[:cached_accessor_name]}
215
215
  fetch_denormalized_cached_association('#{options[:records_variable_name]}', :#{association})
216
216
  end
@@ -235,7 +235,7 @@ module IdentityCache
235
235
  options[:population_method_name] ||= "populate_#{association}_cache"
236
236
  options[:prepopulate_method_name] ||= "prepopulate_fetched_#{association}"
237
237
 
238
- self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
238
+ self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
239
239
  attr_reader :#{options[:ids_variable_name]}
240
240
 
241
241
  def #{options[:cached_ids_name]}
@@ -281,7 +281,7 @@ module IdentityCache
281
281
  child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges)
282
282
  child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration)
283
283
 
284
- child_class.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
284
+ child_class.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
285
285
  after_commit :expire_parent_cache
286
286
  after_touch :expire_parent_cache
287
287
 
@@ -9,7 +9,7 @@ module IdentityCache
9
9
  base.private_class_method :set_embedded_association
10
10
  base.private_class_method :get_embedded_association
11
11
  base.private_class_method :add_cached_associations_to_coder
12
- base.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
12
+ base.instance_eval(<<-CODE, __FILE__, __LINE__ + 1)
13
13
  private :expire_cache, :was_new_record?, :fetch_denormalized_cached_association,
14
14
  :populate_denormalized_cached_association
15
15
  CODE
@@ -26,8 +26,9 @@ module IdentityCache
26
26
  end
27
27
 
28
28
  # Default fetcher added to the model on inclusion, it behaves like
29
- # ActiveRecord::Base.find_by_id
29
+ # ActiveRecord::Base.where(id: id).first
30
30
  def fetch_by_id(id)
31
+ return unless id
31
32
  raise NotImplementedError, "fetching needs the primary index enabled" unless primary_cache_index_enabled
32
33
  if IdentityCache.should_cache?
33
34
 
@@ -40,7 +41,7 @@ module IdentityCache
40
41
  end
41
42
 
42
43
  else
43
- self.find_by_id(id)
44
+ self.where(id: id).first
44
45
  end
45
46
  end
46
47
 
@@ -95,8 +96,10 @@ module IdentityCache
95
96
  @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
96
97
  @association_cache = {}
97
98
  @aggregation_cache = {}
99
+ @_start_transaction_state = {}
98
100
  @readonly = @destroyed = @marked_for_destruction = false
99
101
  @new_record = false
102
+ @column_types = self.class.column_types if self.class.respond_to?(:column_types)
100
103
  end
101
104
  else
102
105
  record.init_with(coder)
@@ -115,7 +118,7 @@ module IdentityCache
115
118
  association = reflection.association_class.new(record, reflection)
116
119
  association.target = coder_or_array.map {|e| record_from_coder(e) }
117
120
  association.target.each {|e| association.set_inverse_instance(e) }
118
- association.proxy
121
+ association
119
122
  else
120
123
  record_from_coder(coder_or_array)
121
124
  end
@@ -175,9 +178,9 @@ module IdentityCache
175
178
  end
176
179
 
177
180
  def resolve_cache_miss(id)
178
- self.find_by_id(id, :include => cache_fetch_includes).tap do |object|
179
- object.try(:populate_association_caches)
180
- end
181
+ object = self.includes(cache_fetch_includes).where(id: id).try(:first)
182
+ object.try(:populate_association_caches)
183
+ object
181
184
  end
182
185
 
183
186
  def all_embedded_associations
@@ -223,7 +226,7 @@ module IdentityCache
223
226
  def find_batch(ids, options = {})
224
227
  @id_column ||= columns.detect {|c| c.name == "id"}
225
228
  ids = ids.map{ |id| @id_column.type_cast(id) }
226
- records = where('id IN (?)', ids).includes(cache_fetch_includes(options[:includes])).all
229
+ records = where('id IN (?)', ids).includes(cache_fetch_includes(options[:includes])).to_a
227
230
  records_by_id = records.index_by(&:id)
228
231
  records = ids.map{ |id| records_by_id[id] }
229
232
  mismatching_ids = records.compact.map(&:id) - ids
@@ -334,7 +337,8 @@ module IdentityCache
334
337
  ivar_full_name = :"@#{ivar_name}"
335
338
  if IdentityCache.should_cache?
336
339
  populate_denormalized_cached_association(ivar_name, association_name)
337
- IdentityCache.unmap_cached_nil_for(instance_variable_get(ivar_full_name))
340
+ assoc = IdentityCache.unmap_cached_nil_for(instance_variable_get(ivar_full_name))
341
+ assoc.is_a?(ActiveRecord::Associations::CollectionAssociation) ? assoc.reader : assoc
338
342
  else
339
343
  send(association_name.to_sym)
340
344
  end
@@ -388,7 +392,14 @@ module IdentityCache
388
392
 
389
393
  def expire_attribute_indexes # :nodoc:
390
394
  cache_attributes.try(:each) do |(attribute, fields)|
391
- IdentityCache.cache.delete(attribute_cache_key_for_attribute_and_previous_values(attribute, fields)) unless was_new_record?
395
+ unless was_new_record?
396
+ old_cache_attribute_key = attribute_cache_key_for_attribute_and_previous_values(attribute, fields)
397
+ IdentityCache.cache.delete(old_cache_attribute_key)
398
+ end
399
+ new_cache_attribute_key = attribute_cache_key_for_attribute_and_current_values(attribute, fields)
400
+ if new_cache_attribute_key != old_cache_attribute_key
401
+ IdentityCache.cache.delete(new_cache_attribute_key)
402
+ end
392
403
  end
393
404
  end
394
405
 
@@ -1,4 +1,4 @@
1
1
  module IdentityCache
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  CACHE_VERSION = 3
4
4
  end
@@ -18,11 +18,11 @@ require File.dirname(__FILE__) + '/../test/helpers/database_connection'
18
18
  require File.dirname(__FILE__) + '/../test/helpers/cache'
19
19
 
20
20
  def create_record(id)
21
- r = Record.new(id)
21
+ Item.new(id)
22
22
  end
23
23
 
24
24
  def database_ready(count)
25
- Record.where(:id => (1..count)).count == count
25
+ Item.where(:id => (1..count)).count == count
26
26
  rescue
27
27
  false
28
28
  end
@@ -35,10 +35,10 @@ def create_database(count)
35
35
 
36
36
  DatabaseConnection.setup
37
37
  # set up associations
38
- Record.cache_has_one :associated
39
- Record.cache_has_many :associated_records, :embed => true
40
- Record.cache_has_many :normalized_associated_records, :embed => false
41
- Record.cache_index :title, :unique => :true
38
+ Item.cache_has_one :associated
39
+ Item.cache_has_many :associated_records, :embed => true
40
+ Item.cache_has_many :normalized_associated_records, :embed => false
41
+ Item.cache_index :title, :unique => :true
42
42
  AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
43
43
 
44
44
  return if database_ready(count)
@@ -46,10 +46,10 @@ def create_database(count)
46
46
 
47
47
  DatabaseConnection.drop_tables
48
48
  DatabaseConnection.create_tables
49
- existing = Record.all
49
+ existing = Item.all
50
50
  (1..count).to_a.each do |i|
51
51
  unless existing.any? { |e| e.id == i }
52
- a = Record.new
52
+ a = Item.new
53
53
  a.id = i
54
54
  a.associated = AssociatedRecord.new(name: "Associated for #{i}")
55
55
  a.associated_records
@@ -77,7 +77,7 @@ end
77
77
  class FindRunner < CacheRunner
78
78
  def run
79
79
  (1..@count).each do |i|
80
- ::Record.find(i)
80
+ ::Item.find(i)
81
81
  end
82
82
  end
83
83
  end
@@ -93,7 +93,7 @@ class FetchMissRunner < CacheRunner
93
93
 
94
94
  def run
95
95
  (1..@count).each do |i|
96
- rec = ::Record.fetch(i)
96
+ rec = ::Item.fetch(i)
97
97
  rec.fetch_associated
98
98
  rec.fetch_associated_records
99
99
  end
@@ -105,7 +105,7 @@ class DoubleFetchMissRunner < CacheRunner
105
105
 
106
106
  def run
107
107
  (1..@count).each do |i|
108
- rec = ::Record.fetch(i)
108
+ rec = ::Item.fetch(i)
109
109
  rec.fetch_associated
110
110
  rec.fetch_associated_records
111
111
  rec.fetch_normalized_associated_records
@@ -117,7 +117,7 @@ module HitRunner
117
117
  def prepare
118
118
  IdentityCache.cache.clear
119
119
  (1..@count).each do |i|
120
- rec = ::Record.fetch(i)
120
+ rec = ::Item.fetch(i)
121
121
  rec.fetch_normalized_associated_records
122
122
  end
123
123
  end
@@ -128,7 +128,7 @@ class FetchHitRunner < CacheRunner
128
128
 
129
129
  def run
130
130
  (1..@count).each do |i|
131
- rec = ::Record.fetch(i)
131
+ rec = ::Item.fetch(i)
132
132
  # these should all be no cost
133
133
  rec.fetch_associated
134
134
  rec.fetch_associated_records
@@ -141,7 +141,7 @@ class DoubleFetchHitRunner < CacheRunner
141
141
 
142
142
  def run
143
143
  (1..@count).each do |i|
144
- rec = ::Record.fetch(i)
144
+ rec = ::Item.fetch(i)
145
145
  # these should all be no cost
146
146
  rec.fetch_associated
147
147
  rec.fetch_associated_records
@@ -149,4 +149,3 @@ class DoubleFetchHitRunner < CacheRunner
149
149
  end
150
150
  end
151
151
  end
152
-
@@ -6,12 +6,11 @@ class AttributeCacheTest < IdentityCache::TestCase
6
6
  def setup
7
7
  super
8
8
  AssociatedRecord.cache_attribute :name
9
- AssociatedRecord.cache_attribute :record, :by => [:id, :name]
10
9
 
11
- @parent = Record.create!(:title => 'bob')
10
+ @parent = Item.create!(:title => 'bob')
12
11
  @record = @parent.associated_records.create!(:name => 'foo')
13
12
  @name_attribute_key = "#{NAMESPACE}attribute:AssociatedRecord:name:id:#{cache_hash(@record.id.to_s)}"
14
- @blob_key = "#{NAMESPACE}blob:AssociatedRecord:#{cache_hash("id:integer,name:string,record_id:integer")}:1"
13
+ @blob_key = "#{NAMESPACE}blob:AssociatedRecord:#{cache_hash("id:integer,item_id:integer,name:string")}:1"
15
14
  end
16
15
 
17
16
  def test_attribute_values_are_returned_on_cache_hits
@@ -21,17 +20,16 @@ class AttributeCacheTest < IdentityCache::TestCase
21
20
 
22
21
  def test_attribute_values_are_fetched_and_returned_on_cache_misses
23
22
  IdentityCache.cache.expects(:read).with(@name_attribute_key).returns(nil)
24
- Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
23
+ Item.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
25
24
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
26
25
  end
27
26
 
28
27
  def test_attribute_values_are_stored_in_the_cache_on_cache_misses
29
-
30
28
  # Cache miss, so
31
29
  IdentityCache.cache.expects(:read).with(@name_attribute_key).returns(nil)
32
30
 
33
31
  # Grab the value of the attribute from the DB
34
- Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
32
+ Item.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
35
33
 
36
34
  # And write it back to the cache
37
35
  IdentityCache.cache.expects(:write).with(@name_attribute_key, 'foo').returns(nil)
@@ -39,6 +37,19 @@ class AttributeCacheTest < IdentityCache::TestCase
39
37
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
40
38
  end
41
39
 
40
+ def test_nil_is_stored_in_the_cache_on_cache_misses
41
+ # Cache miss, so
42
+ IdentityCache.cache.expects(:read).with(@name_attribute_key).returns(nil)
43
+
44
+ # Grab the value of the attribute from the DB
45
+ Item.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns(nil)
46
+
47
+ # And write it back to the cache
48
+ IdentityCache.cache.expects(:write).with(@name_attribute_key, IdentityCache::CACHED_NIL).returns(nil)
49
+
50
+ assert_equal nil, AssociatedRecord.fetch_name_by_id(1)
51
+ end
52
+
42
53
  def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_is_saved
43
54
  IdentityCache.cache.expects(:delete).with(@name_attribute_key)
44
55
  IdentityCache.cache.expects(:delete).with(@blob_key)
@@ -59,17 +70,27 @@ class AttributeCacheTest < IdentityCache::TestCase
59
70
  end
60
71
 
61
72
  def test_cached_attribute_values_are_expired_from_the_cache_when_a_new_record_is_saved
62
- IdentityCache.cache.expects(:delete).with("#{NAMESPACE}blob:AssociatedRecord:#{cache_hash("id:integer,name:string,record_id:integer")}:2")
73
+ new_id = 2.to_s
74
+ # primary index delete
75
+ IdentityCache.cache.expects(:delete).with("#{NAMESPACE}blob:AssociatedRecord:#{cache_hash("id:integer,item_id:integer,name:string")}:#{new_id}")
76
+ # attribute cache delete
77
+ IdentityCache.cache.expects(:delete).with("#{NAMESPACE}attribute:AssociatedRecord:name:id:#{cache_hash(new_id)}")
63
78
  @parent.associated_records.create(:name => 'bar')
64
79
  end
65
80
 
66
81
  def test_fetching_by_attribute_delegates_to_block_if_transactions_are_open
67
82
  IdentityCache.cache.expects(:read).with(@name_attribute_key).never
68
83
 
69
- Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
84
+ Item.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
70
85
 
71
86
  @record.transaction do
72
87
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
73
88
  end
74
89
  end
90
+
91
+ def test_previously_stored_cached_nils_are_busted_by_new_record_saves
92
+ assert_equal nil, AssociatedRecord.fetch_name_by_id(2)
93
+ AssociatedRecord.create(:name => "Jim")
94
+ assert_equal "Jim", AssociatedRecord.fetch_name_by_id(2)
95
+ end
75
96
  end