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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -0
- data/Gemfile.rails40 +1 -0
- data/Gemfile.rails41 +1 -0
- data/Gemfile.rails42 +1 -0
- data/README.md +7 -0
- data/identity_cache.gemspec +1 -1
- data/lib/identity_cache.rb +5 -2
- data/lib/identity_cache/belongs_to_caching.rb +13 -5
- data/lib/identity_cache/cache_key_generation.rb +12 -19
- data/lib/identity_cache/configuration_dsl.rb +83 -84
- data/lib/identity_cache/parent_model_expiration.rb +4 -3
- data/lib/identity_cache/query_api.rb +93 -91
- data/lib/identity_cache/version.rb +2 -2
- data/test/attribute_cache_test.rb +42 -63
- data/test/deeply_nested_associated_record_test.rb +1 -0
- data/test/denormalized_has_many_test.rb +18 -0
- data/test/denormalized_has_one_test.rb +15 -5
- data/test/fetch_multi_test.rb +25 -3
- data/test/fetch_test.rb +20 -7
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +10 -0
- data/test/helpers/database_connection.rb +6 -1
- data/test/helpers/serialization_format.rb +1 -1
- data/test/index_cache_test.rb +50 -25
- data/test/normalized_belongs_to_test.rb +21 -6
- data/test/normalized_has_many_test.rb +44 -0
- data/test/{fetch_multi_with_batched_associations_test.rb → prefetch_normalized_associations_test.rb} +41 -3
- data/test/save_test.rb +14 -14
- data/test/schema_change_test.rb +2 -0
- data/test/test_helper.rb +4 -4
- metadata +11 -10
- data/Gemfile.rails32 +0 -5
- data/test/fixtures/serialized_record +0 -0
@@ -10,6 +10,7 @@ class DeeplyNestedAssociatedRecordHasOneTest < IdentityCache::TestCase
|
|
10
10
|
|
11
11
|
def test_deeply_nested_models_can_cache_has_many_associations
|
12
12
|
assert_nothing_raised do
|
13
|
+
PolymorphicRecord.include(IdentityCache)
|
13
14
|
Deeply::Nested::AssociatedRecord.has_many :polymorphic_records, as: 'owner'
|
14
15
|
Deeply::Nested::AssociatedRecord.cache_has_many :polymorphic_records, inverse_name: :owner
|
15
16
|
end
|
@@ -82,10 +82,28 @@ class DenormalizedHasManyTest < IdentityCache::TestCase
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
def test_cache_uses_inverse_of_on_association
|
86
|
+
Item.has_many :invertable_association, :inverse_of => :owner, :class_name => 'PolymorphicRecord', :as => "owner"
|
87
|
+
Item.cache_has_many :invertable_association, :embed => true
|
88
|
+
end
|
89
|
+
|
85
90
|
def test_saving_associated_records_should_expire_itself_and_the_parents_cache
|
86
91
|
child = @record.associated_records.first
|
87
92
|
IdentityCache.cache.expects(:delete).with(child.primary_cache_index_key).once
|
88
93
|
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
|
89
94
|
child.save!
|
90
95
|
end
|
96
|
+
|
97
|
+
def test_unsupported_through_assocation
|
98
|
+
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
99
|
+
Item.has_many :deeply_through_associated_records, :through => :associated_records, foreign_key: 'associated_record_id', inverse_of: :item, :class_name => 'DeeplyAssociatedRecord'
|
100
|
+
Item.cache_has_many :deeply_through_associated_records, :embed => true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_cache_has_many_on_derived_model_raises
|
105
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
106
|
+
StiRecordTypeA.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
107
|
+
end
|
108
|
+
end
|
91
109
|
end
|
@@ -20,7 +20,7 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
20
20
|
assert_equal @record, record_from_cache_miss
|
21
21
|
assert_not_nil @record.fetch_associated
|
22
22
|
assert_equal @record.associated, record_from_cache_miss.fetch_associated
|
23
|
-
assert fetch.has_been_called_with?(@record.
|
23
|
+
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
24
24
|
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
25
25
|
end
|
26
26
|
|
@@ -29,7 +29,6 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
29
29
|
@record.associated = nil
|
30
30
|
@record.save!
|
31
31
|
@record.reload
|
32
|
-
@record.send(:populate_association_caches)
|
33
32
|
Item.expects(:resolve_cache_miss).with(@record.id).once.returns(@record)
|
34
33
|
|
35
34
|
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
@@ -41,7 +40,7 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
41
40
|
5.times do
|
42
41
|
assert_nil record_from_cache_miss.fetch_associated
|
43
42
|
end
|
44
|
-
assert fetch.has_been_called_with?(@record.
|
43
|
+
assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
|
45
44
|
assert fetch.has_been_called_with?(@record.primary_cache_index_key)
|
46
45
|
end
|
47
46
|
|
@@ -53,7 +52,6 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
53
52
|
end
|
54
53
|
|
55
54
|
def test_on_cache_hit_record_should_come_back_with_cached_association
|
56
|
-
@record.send(:populate_association_caches)
|
57
55
|
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
58
56
|
Item.fetch_by_title('foo')
|
59
57
|
|
@@ -69,7 +67,6 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
69
67
|
@record.save!
|
70
68
|
@record.reload
|
71
69
|
|
72
|
-
@record.send(:populate_association_caches)
|
73
70
|
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
74
71
|
Item.fetch_by_title('foo')
|
75
72
|
|
@@ -110,4 +107,17 @@ class DenormalizedHasOneTest < IdentityCache::TestCase
|
|
110
107
|
Item.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
111
108
|
end
|
112
109
|
end
|
110
|
+
|
111
|
+
def test_unsupported_through_assocation
|
112
|
+
assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
|
113
|
+
Item.has_one :deeply_associated, :through => :associated, :class_name => 'DeeplyAssociatedRecord'
|
114
|
+
Item.cache_has_one :deeply_associated, :embed => true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_cache_has_one_on_derived_model_raises
|
119
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
120
|
+
StiRecordTypeA.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
|
121
|
+
end
|
122
|
+
end
|
113
123
|
end
|
data/test/fetch_multi_test.rb
CHANGED
@@ -121,6 +121,18 @@ class FetchMultiTest < IdentityCache::TestCase
|
|
121
121
|
assert_equal [@joe, @bob, @joe], Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
122
122
|
end
|
123
123
|
|
124
|
+
def test_fetch_multi_with_duplicate_ids_hits_backend_once_per_id
|
125
|
+
cache_response = {
|
126
|
+
@joe_blob_key => cache_response_for(@joe),
|
127
|
+
@bob_blob_key => cache_response_for(@bob),
|
128
|
+
}
|
129
|
+
|
130
|
+
fetcher.expects(:fetch_multi).with([@joe_blob_key, @bob_blob_key]).returns(cache_response)
|
131
|
+
result = Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
132
|
+
|
133
|
+
assert_equal [@joe, @bob, @joe], result
|
134
|
+
end
|
135
|
+
|
124
136
|
def test_fetch_multi_with_open_transactions_hits_the_database
|
125
137
|
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
126
138
|
fetcher.expects(:fetch_multi).never
|
@@ -199,6 +211,18 @@ class FetchMultiTest < IdentityCache::TestCase
|
|
199
211
|
assert_equal cache_response_for(Item.find(@bob.id)), backend.read(@bob.primary_cache_index_key)
|
200
212
|
end
|
201
213
|
|
214
|
+
def test_fetch_multi_raises_when_called_on_a_scope
|
215
|
+
assert_raises(IdentityCache::UnsupportedScopeError) do
|
216
|
+
Item.where(updated_at: nil).fetch_multi(@bob.id, @joe.id, @fred.id)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_fetch_multi_on_derived_model_raises
|
221
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
222
|
+
StiRecordTypeA.fetch_multi(1, 2)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
202
226
|
private
|
203
227
|
|
204
228
|
def populate_only_fred
|
@@ -210,9 +234,7 @@ class FetchMultiTest < IdentityCache::TestCase
|
|
210
234
|
end
|
211
235
|
|
212
236
|
def cache_response_for(record)
|
213
|
-
|
214
|
-
record.encode_with(coder)
|
215
|
-
coder
|
237
|
+
{class: record.class, attributes: record.attributes_before_type_cast}
|
216
238
|
end
|
217
239
|
|
218
240
|
def with_batch_size(size)
|
data/test/fetch_test.rb
CHANGED
@@ -11,17 +11,12 @@ class FetchTest < IdentityCache::TestCase
|
|
11
11
|
@record = Item.new
|
12
12
|
@record.id = 1
|
13
13
|
@record.title = 'bob'
|
14
|
-
@cached_value = {:class
|
15
|
-
@record.encode_with(@cached_value)
|
14
|
+
@cached_value = {class: @record.class, attributes: @record.attributes_before_type_cast}
|
16
15
|
@blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
17
|
-
@index_key = "#{NAMESPACE}
|
16
|
+
@index_key = "#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}"
|
18
17
|
end
|
19
18
|
|
20
19
|
def test_fetch_with_garbage_input
|
21
|
-
Item.connection.expects(:exec_query)
|
22
|
-
.with(Item.where(id: 0).limit(1).to_sql, any_parameters)
|
23
|
-
.returns(ActiveRecord::Result.new([], []))
|
24
|
-
|
25
20
|
assert_equal nil, Item.fetch_by_id('garbage')
|
26
21
|
end
|
27
22
|
|
@@ -199,4 +194,22 @@ class FetchTest < IdentityCache::TestCase
|
|
199
194
|
|
200
195
|
assert_equal false, ActiveRecord::Base.connection_handler.active_connections?
|
201
196
|
end
|
197
|
+
|
198
|
+
def test_fetch_raises_when_called_on_a_scope
|
199
|
+
assert_raises(IdentityCache::UnsupportedScopeError) do
|
200
|
+
Item.where(updated_at: nil).fetch(1)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_fetch_by_raises_when_called_on_a_scope
|
205
|
+
assert_raises(IdentityCache::UnsupportedScopeError) do
|
206
|
+
Item.where(updated_at: nil).fetch_by_id(1)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_fetch_on_derived_model_raises
|
211
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
212
|
+
StiRecordTypeA.fetch(1)
|
213
|
+
end
|
214
|
+
end
|
202
215
|
end
|
Binary file
|
Binary file
|
@@ -76,6 +76,14 @@ module ActiveRecordObjects
|
|
76
76
|
include IdentityCache
|
77
77
|
self.primary_key = "hashed_key"
|
78
78
|
}
|
79
|
+
|
80
|
+
Object.send :const_set, 'StiRecord', Class.new(base) {
|
81
|
+
include IdentityCache
|
82
|
+
has_many :polymorphic_records, :as => 'owner'
|
83
|
+
}
|
84
|
+
|
85
|
+
Object.send :const_set, 'StiRecordTypeA', Class.new(StiRecord) {
|
86
|
+
}
|
79
87
|
end
|
80
88
|
|
81
89
|
def teardown_models
|
@@ -89,6 +97,8 @@ module ActiveRecordObjects
|
|
89
97
|
Object.send :remove_const, 'Item'
|
90
98
|
Object.send :remove_const, 'ItemTwo'
|
91
99
|
Object.send :remove_const, 'KeyedRecord'
|
100
|
+
Object.send :remove_const, 'StiRecord'
|
101
|
+
Object.send :remove_const, 'StiRecordTypeA'
|
92
102
|
Deeply::Nested.send :remove_const, 'AssociatedRecord'
|
93
103
|
Deeply.send :remove_const, 'Nested'
|
94
104
|
Object.send :remove_const, 'Deeply'
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module DatabaseConnection
|
2
|
+
def self.db_name
|
3
|
+
ENV.fetch('DB', 'mysql2')
|
4
|
+
end
|
5
|
+
|
2
6
|
def self.setup
|
3
|
-
db_config = ENV['DATABASE_URL'] || DEFAULT_CONFIG[
|
7
|
+
db_config = ENV['DATABASE_URL'] || DEFAULT_CONFIG[db_name]
|
4
8
|
begin
|
5
9
|
ActiveRecord::Base.establish_connection(db_config)
|
6
10
|
ActiveRecord::Base.connection
|
@@ -39,6 +43,7 @@ module DatabaseConnection
|
|
39
43
|
:items => [[:integer, :item_id], [:string, :title], [:timestamps, null: true]],
|
40
44
|
:items2 => [[:integer, :item_id], [:string, :title], [:timestamps, null: true]],
|
41
45
|
:keyed_records => [[:string, :value], :primary_key => "hashed_key"],
|
46
|
+
:sti_records => [[:string, :type], [:string, :name]],
|
42
47
|
}
|
43
48
|
|
44
49
|
DEFAULT_CONFIG = {
|
@@ -24,7 +24,7 @@ module SerializationFormat
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def serialized_record_file
|
27
|
-
File.expand_path("../../fixtures/serialized_record", __FILE__)
|
27
|
+
File.expand_path("../../fixtures/serialized_record.#{DatabaseConnection.db_name}", __FILE__)
|
28
28
|
end
|
29
29
|
|
30
30
|
def serialize(record, anIO = nil)
|
data/test/index_cache_test.rb
CHANGED
@@ -8,17 +8,14 @@ class IndexCacheTest < IdentityCache::TestCase
|
|
8
8
|
@record = Item.new
|
9
9
|
@record.id = 1
|
10
10
|
@record.title = 'bob'
|
11
|
-
@cache_key = "#{NAMESPACE}index:Item:title:#{cache_hash(@record.title)}"
|
12
11
|
end
|
13
12
|
|
14
|
-
def
|
13
|
+
def test_fetch_with_garbage_input
|
15
14
|
Item.cache_index :title, :id
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
assert_equal [], Item.fetch_by_title_and_id('garbage', 'garbage')
|
16
|
+
assert_queries(1) do
|
17
|
+
assert_equal [], Item.fetch_by_title_and_id('garbage', 'garbage')
|
18
|
+
end
|
22
19
|
end
|
23
20
|
|
24
21
|
def test_fetch_with_unique_adds_limit_clause
|
@@ -34,54 +31,55 @@ class IndexCacheTest < IdentityCache::TestCase
|
|
34
31
|
def test_unique_index_caches_nil
|
35
32
|
Item.cache_index :title, :unique => true
|
36
33
|
assert_equal nil, Item.fetch_by_title('bob')
|
37
|
-
assert_equal IdentityCache::CACHED_NIL, backend.read(
|
34
|
+
assert_equal IdentityCache::CACHED_NIL, backend.read(cache_key(unique: true))
|
38
35
|
end
|
39
36
|
|
40
37
|
def test_unique_index_expired_by_new_record
|
41
38
|
Item.cache_index :title, :unique => true
|
42
|
-
IdentityCache.cache.write(
|
39
|
+
IdentityCache.cache.write(cache_key(unique: true), IdentityCache::CACHED_NIL)
|
43
40
|
@record.save!
|
44
|
-
assert_equal IdentityCache::DELETED, backend.read(
|
41
|
+
assert_equal IdentityCache::DELETED, backend.read(cache_key(unique: true))
|
45
42
|
end
|
46
43
|
|
47
44
|
def test_unique_index_filled_on_fetch_by
|
48
45
|
Item.cache_index :title, :unique => true
|
49
46
|
@record.save!
|
50
47
|
assert_equal @record, Item.fetch_by_title('bob')
|
51
|
-
assert_equal @record.id, backend.read(
|
48
|
+
assert_equal @record.id, backend.read(cache_key(unique: true))
|
52
49
|
end
|
53
50
|
|
54
51
|
def test_unique_index_expired_by_updated_record
|
55
52
|
Item.cache_index :title, :unique => true
|
56
53
|
@record.save!
|
57
|
-
|
54
|
+
old_cache_key = cache_key(unique: true)
|
55
|
+
IdentityCache.cache.write(old_cache_key, @record.id)
|
58
56
|
|
59
57
|
@record.title = 'robert'
|
60
|
-
new_cache_key =
|
58
|
+
new_cache_key = cache_key(unique: true)
|
61
59
|
IdentityCache.cache.write(new_cache_key, IdentityCache::CACHED_NIL)
|
62
60
|
@record.save!
|
63
|
-
assert_equal IdentityCache::DELETED, backend.read(
|
61
|
+
assert_equal IdentityCache::DELETED, backend.read(old_cache_key)
|
64
62
|
assert_equal IdentityCache::DELETED, backend.read(new_cache_key)
|
65
63
|
end
|
66
64
|
|
67
65
|
def test_non_unique_index_caches_empty_result
|
68
66
|
Item.cache_index :title
|
69
67
|
assert_equal [], Item.fetch_by_title('bob')
|
70
|
-
assert_equal [], backend.read(
|
68
|
+
assert_equal [], backend.read(cache_key)
|
71
69
|
end
|
72
70
|
|
73
71
|
def test_non_unique_index_expired_by_new_record
|
74
72
|
Item.cache_index :title
|
75
|
-
IdentityCache.cache.write(
|
73
|
+
IdentityCache.cache.write(cache_key, [])
|
76
74
|
@record.save!
|
77
|
-
assert_equal IdentityCache::DELETED, backend.read(
|
75
|
+
assert_equal IdentityCache::DELETED, backend.read(cache_key)
|
78
76
|
end
|
79
77
|
|
80
78
|
def test_non_unique_index_filled_on_fetch_by
|
81
79
|
Item.cache_index :title
|
82
80
|
@record.save!
|
83
81
|
assert_equal [@record], Item.fetch_by_title('bob')
|
84
|
-
assert_equal [@record.id], backend.read(
|
82
|
+
assert_equal [@record.id], backend.read(cache_key)
|
85
83
|
end
|
86
84
|
|
87
85
|
def test_non_unique_index_fetches_multiple_records
|
@@ -90,28 +88,29 @@ class IndexCacheTest < IdentityCache::TestCase
|
|
90
88
|
record2 = Item.create(:title => 'bob') { |item| item.id = 2 }
|
91
89
|
|
92
90
|
assert_equal [@record, record2], Item.fetch_by_title('bob')
|
93
|
-
assert_equal [1, 2], backend.read(
|
91
|
+
assert_equal [1, 2], backend.read(cache_key)
|
94
92
|
end
|
95
93
|
|
96
94
|
def test_non_unique_index_expired_by_updating_record
|
97
95
|
Item.cache_index :title
|
98
96
|
@record.save!
|
99
|
-
|
97
|
+
old_cache_key = cache_key
|
98
|
+
IdentityCache.cache.write(old_cache_key, [@record.id])
|
100
99
|
|
101
100
|
@record.title = 'robert'
|
102
|
-
new_cache_key =
|
101
|
+
new_cache_key = cache_key
|
103
102
|
IdentityCache.cache.write(new_cache_key, [])
|
104
103
|
@record.save!
|
105
|
-
assert_equal IdentityCache::DELETED, backend.read(
|
104
|
+
assert_equal IdentityCache::DELETED, backend.read(old_cache_key)
|
106
105
|
assert_equal IdentityCache::DELETED, backend.read(new_cache_key)
|
107
106
|
end
|
108
107
|
|
109
108
|
def test_non_unique_index_expired_by_destroying_record
|
110
109
|
Item.cache_index :title
|
111
110
|
@record.save!
|
112
|
-
IdentityCache.cache.write(
|
111
|
+
IdentityCache.cache.write(cache_key, [@record.id])
|
113
112
|
@record.destroy
|
114
|
-
assert_equal IdentityCache::DELETED, backend.read(
|
113
|
+
assert_equal IdentityCache::DELETED, backend.read(cache_key)
|
115
114
|
end
|
116
115
|
|
117
116
|
def test_set_table_name_cache_fetch
|
@@ -119,6 +118,32 @@ class IndexCacheTest < IdentityCache::TestCase
|
|
119
118
|
Item.table_name = 'items2'
|
120
119
|
@record.save!
|
121
120
|
assert_equal [@record], Item.fetch_by_title('bob')
|
122
|
-
assert_equal [@record.id], backend.read(
|
121
|
+
assert_equal [@record.id], backend.read(cache_key)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_fetch_by_index_raises_when_called_on_a_scope
|
125
|
+
Item.cache_index :title
|
126
|
+
assert_raises(IdentityCache::UnsupportedScopeError) do
|
127
|
+
Item.where(updated_at: nil).fetch_by_title('bob')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_fetch_by_unique_index_raises_when_called_on_a_scope
|
132
|
+
Item.cache_index :title, :id, :unique => true
|
133
|
+
assert_raises(IdentityCache::UnsupportedScopeError) do
|
134
|
+
Item.where(updated_at: nil).fetch_by_title_and_id('bob', 2)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_cache_index_on_derived_model_raises
|
139
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
140
|
+
StiRecordTypeA.cache_index :name, :id
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def cache_key(unique: false)
|
147
|
+
"#{NAMESPACE}attr#{unique ? '' : 's'}:Item:id:title:#{cache_hash(@record.title)}"
|
123
148
|
end
|
124
149
|
end
|
@@ -34,15 +34,30 @@ class NormalizedBelongsToTest < IdentityCache::TestCase
|
|
34
34
|
assert_equal @parent_record, @record.fetch_item
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
37
|
+
def test_fetching_the_association_should_assign_the_result_to_an_instance_variable_so_that_successive_accesses_are_cached
|
38
38
|
Item.expects(:fetch_by_id).with(@parent_record.id).returns(@parent_record)
|
39
|
-
@record.fetch_item
|
40
|
-
|
41
|
-
assert_equal @parent_record, @record.
|
39
|
+
assert_equal @parent_record, @record.fetch_item
|
40
|
+
assert_equal false, @record.association(:item).loaded?
|
41
|
+
assert_equal @parent_record, @record.fetch_item
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def test_fetching_the_association_should_cache_nil_and_not_raise_if_the_record_cant_be_found
|
45
45
|
Item.expects(:fetch_by_id).with(@parent_record.id).returns(nil)
|
46
|
-
assert_equal nil, @record.fetch_item
|
46
|
+
assert_equal nil, @record.fetch_item # miss
|
47
|
+
assert_equal nil, @record.fetch_item # hit
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_cache_belongs_to_on_derived_model_raises
|
51
|
+
assert_raises(IdentityCache::DerivedModelError) do
|
52
|
+
StiRecordTypeA.cache_belongs_to :item, :embed => false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_fetching_polymorphic_belongs_to_association
|
57
|
+
PolymorphicRecord.include IdentityCache
|
58
|
+
PolymorphicRecord.cache_belongs_to :owner
|
59
|
+
PolymorphicRecord.create!(owner: @parent_record)
|
60
|
+
|
61
|
+
assert_equal @parent_record, PolymorphicRecord.first.fetch_owner
|
47
62
|
end
|
48
63
|
end
|
@@ -29,6 +29,50 @@ class NormalizedHasManyTest < IdentityCache::TestCase
|
|
29
29
|
assert_equal false, fetched_record.associated_records.loaded?
|
30
30
|
end
|
31
31
|
|
32
|
+
def test_batch_fetching_of_association_for_multiple_parent_records
|
33
|
+
record2 = Item.new(:title => 'two')
|
34
|
+
record2.associated_records << AssociatedRecord.new(:name => 'a')
|
35
|
+
record2.associated_records << AssociatedRecord.new(:name => 'b')
|
36
|
+
record2.save!
|
37
|
+
|
38
|
+
fetched_records = assert_queries(2) do
|
39
|
+
Item.fetch_multi(@record.id, record2.id)
|
40
|
+
end
|
41
|
+
assert_equal [[2, 1], [4, 3]], fetched_records.map(&:cached_associated_record_ids)
|
42
|
+
assert_equal false, fetched_records.any?{ |record| record.associated_records.loaded? }
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_batch_fetching_of_deeply_associated_records
|
46
|
+
Item.has_many :denormalized_associated_records, class_name: 'AssociatedRecord'
|
47
|
+
Item.cache_has_many :denormalized_associated_records, embed: true
|
48
|
+
AssociatedRecord.cache_has_many :deeply_associated_records, embed: :ids
|
49
|
+
@record.associated_records[0].deeply_associated_records << DeeplyAssociatedRecord.new(:name => 'deep1')
|
50
|
+
@record.associated_records[1].deeply_associated_records << DeeplyAssociatedRecord.new(:name => 'deep2')
|
51
|
+
@record.associated_records.each(&:save!)
|
52
|
+
|
53
|
+
fetched_records = assert_queries(4) do
|
54
|
+
Item.fetch(@record.id)
|
55
|
+
end
|
56
|
+
assert_no_queries do
|
57
|
+
assert_equal [[1], [2]], fetched_records.fetch_denormalized_associated_records.map(&:cached_deeply_associated_record_ids)
|
58
|
+
assert_equal false, fetched_records.fetch_denormalized_associated_records.any?{ |record| record.deeply_associated_records.loaded? }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_batch_fetching_stops_with_nil_parent
|
63
|
+
Item.cache_has_one :associated, embed: true
|
64
|
+
AssociatedRecord.cache_has_many :deeply_associated_records, embed: :ids
|
65
|
+
AssociatedRecord.delete_all
|
66
|
+
|
67
|
+
fetched_records = assert_queries(3) do
|
68
|
+
Item.fetch(@record.id)
|
69
|
+
end
|
70
|
+
assert_no_queries do
|
71
|
+
assert_equal @record, fetched_records
|
72
|
+
assert_nil fetched_records.fetch_associated
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
32
76
|
def test_fetching_associated_ids_will_populate_the_value_if_the_record_isnt_from_the_cache
|
33
77
|
assert_equal [2, 1], @record.fetch_associated_record_ids
|
34
78
|
end
|