identity_cache 0.0.4 → 0.0.5
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 -13
- data/.gitignore +1 -1
- data/.ruby-version +1 -0
- data/.travis.yml +6 -2
- data/Gemfile +3 -0
- data/Gemfile.rails4 +8 -0
- data/README.md +2 -1
- data/identity_cache.gemspec +2 -6
- data/lib/identity_cache.rb +9 -1
- data/lib/identity_cache/belongs_to_caching.rb +1 -1
- data/lib/identity_cache/cache_key_generation.rb +13 -4
- data/lib/identity_cache/configuration_dsl.rb +6 -6
- data/lib/identity_cache/query_api.rb +21 -10
- data/lib/identity_cache/version.rb +1 -1
- data/performance/cache_runner.rb +14 -15
- data/test/attribute_cache_test.rb +29 -8
- data/test/cache_fetch_includes_test.rb +25 -25
- data/test/denormalized_has_many_test.rb +15 -13
- data/test/denormalized_has_one_test.rb +22 -16
- data/test/fetch_multi_test.rb +28 -28
- data/test/fetch_multi_with_batched_associations_test.rb +56 -56
- data/test/fetch_test.rb +38 -30
- data/test/fixtures/serialized_record +0 -0
- data/test/helpers/active_record_objects.rb +28 -24
- data/test/helpers/database_connection.rb +5 -5
- data/test/helpers/serialization_format.rb +7 -7
- data/test/index_cache_test.rb +23 -24
- data/test/memoized_cache_proxy_test.rb +5 -0
- data/test/normalized_belongs_to_test.rb +15 -15
- data/test/normalized_has_many_test.rb +28 -23
- data/test/recursive_denormalized_has_many_test.rb +14 -14
- data/test/save_test.rb +18 -18
- data/test/schema_change_test.rb +26 -13
- metadata +26 -60
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
OGQ2MDc5Zjk2NmY3MTcwMmIxNDkzYjczNTZjMmJhNjMwMWI1ZjJjYg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2692fa11451bb0243fcb3450d6f6c2550110afea
|
4
|
+
data.tar.gz: dbd4fa2555037a5ddd90e36f2aea9ae03fff6907
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
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
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
|
-
-
|
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
|
15
|
+
- rvm: jruby
|
12
16
|
before_script:
|
13
17
|
- mysql -e 'create database identity_cache_test'
|
data/Gemfile
CHANGED
data/Gemfile.rails4
ADDED
data/README.md
CHANGED
data/identity_cache.gemspec
CHANGED
@@ -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('
|
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
|
data/lib/identity_cache.rb
CHANGED
@@ -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
|
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?(:
|
23
|
-
klass.
|
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
|
-
|
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
|
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(
|
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(
|
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(
|
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(
|
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(
|
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.
|
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.
|
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
|
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.
|
179
|
-
|
180
|
-
|
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])).
|
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
|
-
|
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
|
|
data/performance/cache_runner.rb
CHANGED
@@ -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
|
-
|
21
|
+
Item.new(id)
|
22
22
|
end
|
23
23
|
|
24
24
|
def database_ready(count)
|
25
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 =
|
49
|
+
existing = Item.all
|
50
50
|
(1..count).to_a.each do |i|
|
51
51
|
unless existing.any? { |e| e.id == i }
|
52
|
-
a =
|
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
|
-
::
|
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 = ::
|
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 = ::
|
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 = ::
|
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 = ::
|
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 = ::
|
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 =
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|