identity_cache 0.4.1 → 1.1.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.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +92 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/CAVEATS.md +25 -0
- data/CHANGELOG.md +73 -19
- data/Gemfile +5 -1
- data/LICENSE +1 -1
- data/README.md +49 -27
- data/Rakefile +14 -5
- data/dev.yml +12 -16
- data/gemfiles/Gemfile.latest-release +8 -0
- data/gemfiles/Gemfile.min-supported +7 -0
- data/gemfiles/Gemfile.rails-edge +7 -0
- data/identity_cache.gemspec +29 -10
- data/lib/identity_cache.rb +78 -51
- data/lib/identity_cache/belongs_to_caching.rb +12 -40
- data/lib/identity_cache/cache_fetcher.rb +6 -5
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +4 -11
- data/lib/identity_cache/cache_key_generation.rb +17 -65
- data/lib/identity_cache/cache_key_loader.rb +128 -0
- data/lib/identity_cache/cached.rb +7 -0
- data/lib/identity_cache/cached/association.rb +87 -0
- data/lib/identity_cache/cached/attribute.rb +123 -0
- data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
- data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
- data/lib/identity_cache/cached/belongs_to.rb +100 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +61 -0
- data/lib/identity_cache/cached/primary_index.rb +96 -0
- data/lib/identity_cache/cached/recursive/association.rb +109 -0
- data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
- data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
- data/lib/identity_cache/cached/reference/association.rb +16 -0
- data/lib/identity_cache/cached/reference/has_many.rb +105 -0
- data/lib/identity_cache/cached/reference/has_one.rb +100 -0
- data/lib/identity_cache/configuration_dsl.rb +53 -215
- data/lib/identity_cache/encoder.rb +95 -0
- data/lib/identity_cache/expiry_hook.rb +36 -0
- data/lib/identity_cache/fallback_fetcher.rb +2 -1
- data/lib/identity_cache/load_strategy/eager.rb +28 -0
- data/lib/identity_cache/load_strategy/lazy.rb +71 -0
- data/lib/identity_cache/load_strategy/load_request.rb +20 -0
- data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
- data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
- data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
- data/lib/identity_cache/parent_model_expiration.rb +46 -11
- data/lib/identity_cache/query_api.rb +102 -408
- data/lib/identity_cache/railtie.rb +8 -0
- data/lib/identity_cache/record_not_found.rb +6 -0
- data/lib/identity_cache/should_use_cache.rb +1 -0
- data/lib/identity_cache/version.rb +3 -2
- data/lib/identity_cache/with_primary_index.rb +136 -0
- data/lib/identity_cache/without_primary_index.rb +24 -3
- data/performance/cache_runner.rb +25 -73
- data/performance/cpu.rb +4 -3
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +60 -73
- data/.travis.yml +0 -30
- data/Gemfile.rails42 +0 -6
- data/Gemfile.rails50 +0 -6
- data/test/attribute_cache_test.rb +0 -110
- data/test/cache_fetch_includes_test.rb +0 -46
- data/test/cache_hash_test.rb +0 -14
- data/test/cache_invalidation_test.rb +0 -139
- data/test/deeply_nested_associated_record_test.rb +0 -19
- data/test/denormalized_has_many_test.rb +0 -211
- data/test/denormalized_has_one_test.rb +0 -160
- data/test/fetch_multi_test.rb +0 -308
- data/test/fetch_test.rb +0 -258
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +0 -106
- data/test/helpers/database_connection.rb +0 -72
- data/test/helpers/serialization_format.rb +0 -42
- data/test/helpers/update_serialization_format.rb +0 -24
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -49
- data/test/memoized_cache_proxy_test.rb +0 -107
- data/test/normalized_belongs_to_test.rb +0 -107
- data/test/normalized_has_many_test.rb +0 -231
- data/test/normalized_has_one_test.rb +0 -9
- data/test/prefetch_associations_test.rb +0 -364
- data/test/readonly_test.rb +0 -109
- data/test/recursive_denormalized_has_many_test.rb +0 -131
- data/test/save_test.rb +0 -82
- data/test/schema_change_test.rb +0 -112
- data/test/serialization_format_change_test.rb +0 -16
- data/test/test_helper.rb +0 -140
@@ -1,139 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class CacheInvalidationTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
|
7
|
-
@record = Item.new(:title => 'foo')
|
8
|
-
@record.associated_records << AssociatedRecord.new(:name => 'bar')
|
9
|
-
@record.associated_records << AssociatedRecord.new(:name => 'baz')
|
10
|
-
@record.save
|
11
|
-
@record.reload
|
12
|
-
@baz, @bar = @record.associated_records[0], @record.associated_records[1]
|
13
|
-
@record.reload
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_reload_invalidate_cached_ids
|
17
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
18
|
-
|
19
|
-
variable_name = "@#{@record.class.send(:embedded_associations)[:associated_records][:ids_variable_name]}"
|
20
|
-
|
21
|
-
@record.fetch_associated_record_ids
|
22
|
-
assert_equal [@baz.id, @bar.id], @record.instance_variable_get(variable_name)
|
23
|
-
|
24
|
-
@record.reload
|
25
|
-
assert_equal false, @record.instance_variable_defined?(variable_name)
|
26
|
-
|
27
|
-
@record.fetch_associated_record_ids
|
28
|
-
assert_equal [@baz.id, @bar.id], @record.instance_variable_get(variable_name)
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_reload_invalidate_cached_objects
|
32
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
33
|
-
|
34
|
-
variable_name = "@#{@record.class.send(:embedded_associations)[:associated_records][:records_variable_name]}"
|
35
|
-
|
36
|
-
@record.fetch_associated_records
|
37
|
-
assert_equal [@baz, @bar], @record.instance_variable_get(variable_name)
|
38
|
-
|
39
|
-
@record.reload
|
40
|
-
assert_equal false, @record.instance_variable_defined?(variable_name)
|
41
|
-
|
42
|
-
@record.fetch_associated_records
|
43
|
-
assert_equal [@baz, @bar], @record.instance_variable_get(variable_name)
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_after_a_reload_the_cache_perform_as_expected
|
47
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
48
|
-
|
49
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
50
|
-
assert_equal [@baz, @bar], @record.associated_records
|
51
|
-
|
52
|
-
@baz.destroy
|
53
|
-
@record.reload
|
54
|
-
|
55
|
-
assert_equal [@bar], @record.fetch_associated_records
|
56
|
-
assert_equal [@bar], @record.associated_records
|
57
|
-
end
|
58
|
-
|
59
|
-
def test_cache_invalidation_expire_properly_if_child_is_embed_in_multiple_parents
|
60
|
-
Item.cache_has_many :associated_records, :embed => true
|
61
|
-
ItemTwo.cache_has_many :associated_records, :embed => true
|
62
|
-
|
63
|
-
baz = AssociatedRecord.new(:name => 'baz')
|
64
|
-
|
65
|
-
record1 = Item.new(:title => 'foo')
|
66
|
-
record1.associated_records << baz
|
67
|
-
record1.save!
|
68
|
-
|
69
|
-
record2 = ItemTwo.new(:title => 'bar')
|
70
|
-
record2.associated_records << baz
|
71
|
-
record2.save!
|
72
|
-
|
73
|
-
record1.class.fetch(record1.id)
|
74
|
-
record2.class.fetch(record2.id)
|
75
|
-
|
76
|
-
expected_keys = [
|
77
|
-
record1.primary_cache_index_key,
|
78
|
-
record2.primary_cache_index_key,
|
79
|
-
]
|
80
|
-
|
81
|
-
expected_keys.each do |expected_key|
|
82
|
-
assert IdentityCache.cache.fetch(expected_key) { nil }
|
83
|
-
end
|
84
|
-
|
85
|
-
baz.save!
|
86
|
-
|
87
|
-
expected_keys.each do |expected_key|
|
88
|
-
refute IdentityCache.cache.fetch(expected_key) { nil }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def test_cache_invalidation_expire_properly_if_child_is_embed_in_multiple_parents_with_ids
|
93
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
94
|
-
ItemTwo.cache_has_many :associated_records, :embed => :ids
|
95
|
-
|
96
|
-
baz = AssociatedRecord.new(:name => 'baz')
|
97
|
-
|
98
|
-
record1 = Item.new(:title => 'foo')
|
99
|
-
record1.save
|
100
|
-
|
101
|
-
record2 = ItemTwo.new(:title => 'bar')
|
102
|
-
record2.save
|
103
|
-
|
104
|
-
record1.class.fetch(record1.id)
|
105
|
-
record2.class.fetch(record2.id)
|
106
|
-
|
107
|
-
expected_keys = [
|
108
|
-
record1.primary_cache_index_key,
|
109
|
-
record2.primary_cache_index_key,
|
110
|
-
]
|
111
|
-
|
112
|
-
expected_keys.each do |expected_key|
|
113
|
-
assert IdentityCache.cache.fetch(expected_key) { nil }
|
114
|
-
end
|
115
|
-
|
116
|
-
baz.item = record1
|
117
|
-
baz.item_two = record2
|
118
|
-
baz.save!
|
119
|
-
|
120
|
-
expected_keys.each do |expected_key|
|
121
|
-
refute IdentityCache.cache.fetch(expected_key) { nil }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_dedup_cache_invalidation_of_records_embedded_twice_through_different_associations
|
126
|
-
Item.cache_has_many :associated_records, embed: true
|
127
|
-
AssociatedRecord.cache_has_many :deeply_associated_records, embed: true
|
128
|
-
Item.cache_has_many :deeply_associated_records, embed: true
|
129
|
-
|
130
|
-
deeply_associated_record = DeeplyAssociatedRecord.new(name: 'deep', item_id: @record.id)
|
131
|
-
@record.associated_records[0].deeply_associated_records << deeply_associated_record
|
132
|
-
deeply_associated_record.reload
|
133
|
-
|
134
|
-
Item.any_instance.expects(:expire_primary_index).once
|
135
|
-
|
136
|
-
deeply_associated_record.name = "deep2"
|
137
|
-
deeply_associated_record.save!
|
138
|
-
end
|
139
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DeeplyNestedAssociatedRecordHasOneTest < IdentityCache::TestCase
|
4
|
-
def test_deeply_nested_models_can_cache_has_one_associations
|
5
|
-
assert_nothing_raised do
|
6
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
7
|
-
Deeply::Nested::AssociatedRecord.has_one :polymorphic_record, as: 'owner'
|
8
|
-
Deeply::Nested::AssociatedRecord.cache_has_one :polymorphic_record, inverse_name: :owner
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_deeply_nested_models_can_cache_has_many_associations
|
13
|
-
assert_nothing_raised do
|
14
|
-
PolymorphicRecord.include(IdentityCache)
|
15
|
-
Deeply::Nested::AssociatedRecord.has_many :polymorphic_records, as: 'owner'
|
16
|
-
Deeply::Nested::AssociatedRecord.cache_has_many :polymorphic_records, inverse_name: :owner
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,211 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DenormalizedHasManyTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
7
|
-
Item.cache_has_many :associated_records, :embed => true
|
8
|
-
|
9
|
-
@record = Item.new(:title => 'foo')
|
10
|
-
@record.associated_records << AssociatedRecord.new(:name => 'bar')
|
11
|
-
@record.associated_records << AssociatedRecord.new(:name => 'baz')
|
12
|
-
@record.save
|
13
|
-
@record.reload
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_uncached_record_from_the_db_should_come_back_with_association_array
|
17
|
-
record_from_db = Item.find(@record.id)
|
18
|
-
assert_equal Array, record_from_db.fetch_associated_records.class
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_uncached_record_from_the_db_will_use_normal_association
|
22
|
-
expected = @record.associated_records
|
23
|
-
record_from_db = Item.find(@record.id)
|
24
|
-
|
25
|
-
Item.any_instance.expects(:association).with(:associated_records).returns(expected)
|
26
|
-
|
27
|
-
assert_equal @record, record_from_db
|
28
|
-
assert_equal expected, record_from_db.fetch_associated_records
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_on_cache_hit_record_should_come_back_with_cached_association_array
|
32
|
-
Item.fetch(@record.id) # warm cache
|
33
|
-
|
34
|
-
record_from_cache_hit = Item.fetch(@record.id)
|
35
|
-
assert_equal @record, record_from_cache_hit
|
36
|
-
assert_equal Array, record_from_cache_hit.fetch_associated_records.class
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_on_cache_hit_record_should_come_back_with_cached_association
|
40
|
-
Item.fetch(@record.id) # warm cache
|
41
|
-
|
42
|
-
record_from_cache_hit = Item.fetch(@record.id)
|
43
|
-
assert_equal @record, record_from_cache_hit
|
44
|
-
|
45
|
-
expected = @record.associated_records
|
46
|
-
|
47
|
-
assoc = mock()
|
48
|
-
assoc.expects(:klass).returns(Item)
|
49
|
-
Item.any_instance.expects(:association).with(:associated_records).returns(assoc).once
|
50
|
-
|
51
|
-
assert_equal expected, record_from_cache_hit.fetch_associated_records
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_on_cache_miss_record_should_embed_associated_objects_and_return
|
55
|
-
record_from_cache_miss = Item.fetch(@record.id)
|
56
|
-
expected = @record.associated_records
|
57
|
-
|
58
|
-
assert_equal @record, record_from_cache_miss
|
59
|
-
assert_equal expected, record_from_cache_miss.fetch_associated_records
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_changes_in_associated_records_should_expire_the_parents_cache
|
63
|
-
Item.fetch(@record.id)
|
64
|
-
key = @record.primary_cache_index_key
|
65
|
-
assert_not_nil IdentityCache.cache.fetch(key)
|
66
|
-
|
67
|
-
IdentityCache.cache.expects(:delete).with(@record.associated_records.first.primary_cache_index_key)
|
68
|
-
IdentityCache.cache.expects(:delete).with(key)
|
69
|
-
@record.associated_records.first.save
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_changes_in_associated_records_foreign_keys_should_expire_new_parent_and_old_parents_cache
|
73
|
-
@associatated_record = @record.associated_records.first
|
74
|
-
old_key = @record.primary_cache_index_key
|
75
|
-
@new_record = Item.create!
|
76
|
-
new_key = @new_record.primary_cache_index_key
|
77
|
-
|
78
|
-
IdentityCache.cache.expects(:delete).with(@associatated_record.primary_cache_index_key)
|
79
|
-
IdentityCache.cache.expects(:delete).with(old_key)
|
80
|
-
IdentityCache.cache.expects(:delete).with(new_key)
|
81
|
-
@associatated_record.item = @new_record
|
82
|
-
@associatated_record.save!
|
83
|
-
end
|
84
|
-
|
85
|
-
def test_cached_associations_after_commit_hook_will_not_fail_on_undefined_parent_association
|
86
|
-
ar = AssociatedRecord.new
|
87
|
-
ar.save
|
88
|
-
assert_nothing_raised { ar.expire_parent_caches }
|
89
|
-
end
|
90
|
-
|
91
|
-
def test_cache_without_guessable_inverse_name_raises
|
92
|
-
assert_raises IdentityCache::InverseAssociationError do
|
93
|
-
Item.cache_has_many :polymorphic_records, :embed => true
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def test_cache_without_guessable_inverse_name_does_not_raise_when_inverse_name_specified
|
98
|
-
assert_nothing_raised do
|
99
|
-
Item.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def test_cache_uses_inverse_of_on_association
|
104
|
-
Item.has_many :invertable_association, :inverse_of => :owner, :class_name => 'PolymorphicRecord', :as => "owner"
|
105
|
-
Item.cache_has_many :invertable_association, :embed => true
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_saving_associated_records_should_expire_itself_and_the_parents_cache
|
109
|
-
child = @record.associated_records.first
|
110
|
-
IdentityCache.cache.expects(:delete).with(child.primary_cache_index_key).once
|
111
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
|
112
|
-
child.save!
|
113
|
-
end
|
114
|
-
|
115
|
-
def test_fetch_association_does_not_allow_chaining
|
116
|
-
check = proc { assert_equal false, Item.fetch(@record.id).fetch_associated_records.respond_to?(:where) }
|
117
|
-
2.times { check.call } # for miss and hit
|
118
|
-
Item.transaction { check.call }
|
119
|
-
end
|
120
|
-
|
121
|
-
def test_never_set_inverse_association_on_cache_hit
|
122
|
-
Item.fetch(@record.id) # warm cache
|
123
|
-
|
124
|
-
item = IdentityCache.with_never_set_inverse_association do
|
125
|
-
Item.fetch(@record.id)
|
126
|
-
end
|
127
|
-
|
128
|
-
associated_record = item.fetch_associated_records.to_a.first
|
129
|
-
assert item.object_id != associated_record.item.object_id
|
130
|
-
end
|
131
|
-
|
132
|
-
def test_deprecated_set_inverse_association_on_cache_hit
|
133
|
-
Item.fetch(@record.id) # warm cache
|
134
|
-
|
135
|
-
item = Item.fetch(@record.id)
|
136
|
-
|
137
|
-
associated_record = item.fetch_associated_records.to_a.first
|
138
|
-
assert_equal item.object_id, associated_record.item.object_id
|
139
|
-
end
|
140
|
-
|
141
|
-
def test_returned_records_should_be_readonly_on_cache_hit
|
142
|
-
IdentityCache.with_fetch_read_only_records do
|
143
|
-
Item.fetch(@record.id) # warm cache
|
144
|
-
record_from_cache_hit = Item.fetch(@record.id)
|
145
|
-
assert record_from_cache_hit.fetch_associated_records.all?(&:readonly?)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def test_returned_records_should_be_readonly_on_cache_miss
|
150
|
-
IdentityCache.with_fetch_read_only_records do
|
151
|
-
record_from_cache_miss = Item.fetch(@record.id)
|
152
|
-
assert record_from_cache_miss.fetch_associated_records.all?(&:readonly?)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def test_db_returned_records_should_never_be_readonly
|
157
|
-
IdentityCache.with_fetch_read_only_records do
|
158
|
-
record_from_db = Item.find(@record.id)
|
159
|
-
uncached_records = record_from_db.associated_records
|
160
|
-
assert uncached_records.none?(&:readonly?)
|
161
|
-
assert record_from_db.fetch_associated_records.all?(&:readonly?)
|
162
|
-
assert record_from_db.associated_records.none?(&:readonly?)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def test_returned_records_with_open_transactions_should_not_be_readonly
|
167
|
-
IdentityCache.with_fetch_read_only_records do
|
168
|
-
Item.transaction do
|
169
|
-
assert_equal IdentityCache.should_use_cache?, false
|
170
|
-
assert Item.fetch(@record.id).fetch_associated_records.none?(&:readonly?)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def test_respect_should_use_cache_from_embedded_records
|
176
|
-
Item.fetch(@record.id)
|
177
|
-
AssociatedRecord.stubs(:should_use_cache?).returns(false)
|
178
|
-
|
179
|
-
assert_memcache_operations(1) do
|
180
|
-
assert_queries(1) do
|
181
|
-
Item.fetch(@record.id).fetch_associated_records
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
class CheckAssociationTest < IdentityCache::TestCase
|
187
|
-
def test_unsupported_through_assocation
|
188
|
-
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
189
|
-
Item.has_many :deeply_through_associated_records, :through => :associated_records, foreign_key: 'associated_record_id', inverse_of: :item, :class_name => 'DeeplyAssociatedRecord'
|
190
|
-
Item.cache_has_many :deeply_through_associated_records, :embed => true
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def test_unsupported_joins_in_assocation_scope
|
195
|
-
scope = -> { joins(:associated_record).where(associated_records: { name: 'contrived example' }) }
|
196
|
-
Item.has_many :deeply_joined_associated_records, scope, inverse_of: :item, class_name: 'DeeplyAssociatedRecord'
|
197
|
-
Item.cache_has_many :deeply_joined_associated_records, :embed => true
|
198
|
-
|
199
|
-
message = "caching association Item.deeply_joined_associated_records scoped with a join isn't supported"
|
200
|
-
assert_raises IdentityCache::UnsupportedAssociationError, message do
|
201
|
-
Item.fetch(1)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def test_cache_has_many_on_derived_model_raises
|
206
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
207
|
-
StiRecordTypeA.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
@@ -1,160 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class DenormalizedHasOneTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
7
|
-
Item.cache_has_one :associated
|
8
|
-
Item.cache_index :title, :unique => true
|
9
|
-
@record = Item.new(:title => 'foo')
|
10
|
-
@record.associated = AssociatedRecord.new(:name => 'bar')
|
11
|
-
@record.save
|
12
|
-
|
13
|
-
@record.reload
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_on_cache_miss_record_should_embed_associated_object
|
17
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
18
|
-
|
19
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
20
|
-
|
21
|
-
assert_equal @record, record_from_cache_miss
|
22
|
-
assert_not_nil @record.fetch_associated
|
23
|
-
assert_equal @record.associated, record_from_cache_miss.fetch_associated
|
24
|
-
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
25
|
-
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_on_cache_miss_record_should_embed_nil_object
|
29
|
-
|
30
|
-
@record.associated = nil
|
31
|
-
@record.save!
|
32
|
-
@record.reload
|
33
|
-
Item.expects(:resolve_cache_miss).with(@record.id).once.returns(@record)
|
34
|
-
|
35
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
36
|
-
|
37
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
38
|
-
record_from_cache_miss.expects(:associated).never
|
39
|
-
|
40
|
-
assert_equal @record, record_from_cache_miss
|
41
|
-
5.times do
|
42
|
-
assert_nil record_from_cache_miss.fetch_associated
|
43
|
-
end
|
44
|
-
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
45
|
-
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_on_record_from_the_db_will_use_normal_association
|
49
|
-
record_from_db = Item.find_by_title('foo')
|
50
|
-
|
51
|
-
assert_equal @record, record_from_db
|
52
|
-
assert_not_nil record_from_db.fetch_associated
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_on_cache_hit_record_should_come_back_with_cached_association
|
56
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
57
|
-
Item.fetch_by_title('foo')
|
58
|
-
|
59
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
60
|
-
expected = @record.associated
|
61
|
-
|
62
|
-
assert_equal @record, record_from_cache_hit
|
63
|
-
assert_equal expected, record_from_cache_hit.fetch_associated
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_on_cache_hit_record_should_come_back_with_cached_nil_association
|
67
|
-
@record.associated = nil
|
68
|
-
@record.save!
|
69
|
-
@record.reload
|
70
|
-
|
71
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
72
|
-
Item.fetch_by_title('foo')
|
73
|
-
|
74
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
75
|
-
record_from_cache_hit.expects(:associated).never
|
76
|
-
|
77
|
-
assert_equal @record, record_from_cache_hit
|
78
|
-
5.times do
|
79
|
-
assert_nil record_from_cache_hit.fetch_associated
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def test_changes_in_associated_record_should_expire_the_parents_cache
|
84
|
-
Item.fetch_by_title('foo')
|
85
|
-
key = @record.primary_cache_index_key
|
86
|
-
assert_not_nil IdentityCache.cache.fetch(key)
|
87
|
-
|
88
|
-
IdentityCache.cache.expects(:delete).at_least(1).with(key)
|
89
|
-
IdentityCache.cache.expects(:delete).with(@record.associated.primary_cache_index_key)
|
90
|
-
|
91
|
-
@record.associated.save
|
92
|
-
end
|
93
|
-
|
94
|
-
def test_cached_associations_after_commit_hook_will_not_fail_on_undefined_parent_association
|
95
|
-
ar = AssociatedRecord.new
|
96
|
-
ar.save
|
97
|
-
assert_nothing_raised { ar.expire_parent_caches }
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_cache_without_guessable_inverse_name_raises
|
101
|
-
assert_raises IdentityCache::InverseAssociationError do
|
102
|
-
Item.cache_has_one :polymorphic_record, :embed => true
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def test_cache_without_guessable_inverse_name_does_not_raise_when_inverse_name_specified
|
107
|
-
assert_nothing_raised do
|
108
|
-
Item.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def test_unsupported_through_assocation
|
113
|
-
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
114
|
-
Item.has_one :deeply_associated, :through => :associated, :class_name => 'DeeplyAssociatedRecord'
|
115
|
-
Item.cache_has_one :deeply_associated, :embed => true
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_cache_has_one_on_derived_model_raises
|
120
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
121
|
-
StiRecordTypeA.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_returned_record_should_be_readonly_on_cache_hit
|
126
|
-
IdentityCache.with_fetch_read_only_records do
|
127
|
-
Item.fetch_by_title('foo')
|
128
|
-
record_from_cache_hit = Item.fetch_by_title('foo')
|
129
|
-
assert record_from_cache_hit.fetch_associated.readonly?
|
130
|
-
refute record_from_cache_hit.associated.readonly?
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def test_returned_record_should_be_readonly_on_cache_miss
|
135
|
-
IdentityCache.with_fetch_read_only_records do
|
136
|
-
assert IdentityCache.should_use_cache?
|
137
|
-
record_from_cache_miss = Item.fetch_by_title('foo')
|
138
|
-
assert record_from_cache_miss.fetch_associated.readonly?
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_db_returned_record_should_never_be_readonly
|
143
|
-
IdentityCache.with_fetch_read_only_records do
|
144
|
-
record_from_db = Item.find_by_title('foo')
|
145
|
-
uncached_record = record_from_db.associated
|
146
|
-
refute uncached_record.readonly?
|
147
|
-
record_from_db.fetch_associated
|
148
|
-
refute uncached_record.readonly?
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def test_returned_record_with_open_transactions_should_not_be_readonly
|
153
|
-
IdentityCache.with_fetch_read_only_records do
|
154
|
-
Item.transaction do
|
155
|
-
refute IdentityCache.should_use_cache?
|
156
|
-
refute Item.fetch_by_title('foo').fetch_associated.readonly?
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|