identity_cache 0.5.1 → 1.0.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 +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +24 -9
- data/CHANGELOG.md +21 -0
- data/Gemfile +5 -1
- data/README.md +28 -26
- data/Rakefile +14 -5
- data/dev.yml +9 -16
- data/gemfiles/Gemfile.latest-release +6 -0
- data/gemfiles/Gemfile.rails-edge +6 -0
- data/gemfiles/Gemfile.rails52 +6 -0
- data/identity_cache.gemspec +26 -10
- data/lib/identity_cache.rb +49 -46
- 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 +93 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +51 -0
- data/lib/identity_cache/cached/primary_index.rb +97 -0
- data/lib/identity_cache/cached/recursive/association.rb +68 -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/memoized_cache_proxy.rb +127 -58
- data/lib/identity_cache/parent_model_expiration.rb +45 -11
- data/lib/identity_cache/query_api.rb +128 -394
- 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 +28 -34
- data/performance/cpu.rb +3 -2
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +44 -73
- 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 -214
- 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 -51
- data/test/helpers/update_serialization_format.rb +0 -27
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -59
- 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 -379
- 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,231 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class NormalizedHasManyTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
7
|
-
|
8
|
-
@record = Item.new(:title => 'foo')
|
9
|
-
@record.not_cached_records << NotCachedRecord.new(:name => 'NoCache')
|
10
|
-
@record.associated_records << AssociatedRecord.new(:name => 'bar')
|
11
|
-
@record.associated_records << AssociatedRecord.new(:name => 'baz')
|
12
|
-
@record.save
|
13
|
-
@record.reload
|
14
|
-
@baz, @bar = @record.associated_records[0], @record.associated_records[1]
|
15
|
-
@not_cached = @record.not_cached_records.first
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_a_records_list_of_associated_ids_on_the_parent_record_retains_association_sort_order
|
19
|
-
assert_equal [2, 1], @record.associated_record_ids
|
20
|
-
|
21
|
-
AssociatedRecord.create(name: 'foo', item_id: @record.id)
|
22
|
-
@record.reload
|
23
|
-
assert_equal [3, 2, 1], @record.associated_record_ids
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_defining_a_denormalized_has_many_cache_caches_the_list_of_associated_ids_on_the_parent_record_during_cache_miss
|
27
|
-
fetched_record = Item.fetch(@record.id)
|
28
|
-
assert_equal [2, 1], fetched_record.cached_associated_record_ids
|
29
|
-
assert_equal false, fetched_record.associated_records.loaded?
|
30
|
-
end
|
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
|
-
|
76
|
-
def test_fetching_associated_ids_will_populate_the_value_if_the_record_isnt_from_the_cache
|
77
|
-
assert_equal [2, 1], @record.fetch_associated_record_ids
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_fetching_associated_ids_will_use_the_cached_value_if_the_record_is_from_the_cache
|
81
|
-
@record = Item.fetch(@record.id)
|
82
|
-
assert_queries(0) do
|
83
|
-
assert_equal [2, 1], @record.fetch_associated_record_ids
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def test_the_cached_the_list_of_associated_ids_on_the_parent_record_should_not_be_populated_by_default
|
88
|
-
assert_nil @record.cached_associated_record_ids
|
89
|
-
end
|
90
|
-
|
91
|
-
def test_fetching_the_association_should_fetch_each_record_by_id
|
92
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_fetching_the_association_from_a_record_on_a_cache_hit_should_not_issue_any_queries
|
96
|
-
# Populate the cache
|
97
|
-
@record = Item.fetch(@record.id)
|
98
|
-
@record.fetch_associated_records
|
99
|
-
assert_queries(0) do
|
100
|
-
@record = Item.fetch(@record.id)
|
101
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def test_fetching_the_association_from_a_identity_cached_record_should_not_re_fetch_the_association_ids
|
106
|
-
@record = Item.fetch(@record.id)
|
107
|
-
@record.expects(:associated_record_ids).never
|
108
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
109
|
-
end
|
110
|
-
|
111
|
-
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_any_transaction_are_open
|
112
|
-
@record = Item.fetch(@record.id)
|
113
|
-
|
114
|
-
assert_memcache_operations(0) do
|
115
|
-
@record.transaction do
|
116
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def test_fetching_the_association_should_delegate_to_the_normal_association_fetcher_if_the_normal_association_is_loaded
|
122
|
-
# Warm the ActiveRecord association
|
123
|
-
@record.associated_records.to_a
|
124
|
-
|
125
|
-
assert_memcache_operations(0) do
|
126
|
-
assert_equal [@baz, @bar], @record.fetch_associated_records
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def test_saving_a_child_record_shouldnt_expire_the_parents_blob_if_the_foreign_key_hasnt_changed
|
131
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).never
|
132
|
-
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key)
|
133
|
-
@baz.name = 'foo'
|
134
|
-
@baz.save!
|
135
|
-
assert_equal [@baz.id, @bar.id], Item.fetch(@record.id).cached_associated_record_ids
|
136
|
-
assert_equal [@baz, @bar], Item.fetch(@record.id).fetch_associated_records
|
137
|
-
end
|
138
|
-
|
139
|
-
def test_creating_a_child_record_should_expire_the_parents_cache_blob
|
140
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
141
|
-
IdentityCache.cache.expects(:delete).with(@bar.primary_cache_index_key[0...-1] + '3')
|
142
|
-
@qux = @record.associated_records.create!(:name => 'qux')
|
143
|
-
assert_equal [@qux, @baz, @bar], Item.fetch(@record.id).fetch_associated_records
|
144
|
-
end
|
145
|
-
|
146
|
-
def test_saving_a_child_record_should_expire_the_new_and_old_parents_cache_blob
|
147
|
-
@new_record = Item.create
|
148
|
-
@baz.item = @new_record
|
149
|
-
|
150
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
151
|
-
IdentityCache.cache.expects(:delete).with(@new_record.primary_cache_index_key).once
|
152
|
-
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key).once
|
153
|
-
|
154
|
-
@baz.save!
|
155
|
-
|
156
|
-
assert_equal [@bar.id], Item.fetch(@record.id).cached_associated_record_ids
|
157
|
-
assert_equal [@bar], Item.fetch(@record.id).fetch_associated_records
|
158
|
-
end
|
159
|
-
|
160
|
-
def test_saving_a_child_record_in_a_transaction_should_expire_the_new_and_old_parents_cache_blob
|
161
|
-
@new_record = Item.create
|
162
|
-
@baz.item_id = @new_record.id
|
163
|
-
|
164
|
-
@baz.transaction do
|
165
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
166
|
-
IdentityCache.cache.expects(:delete).with(@new_record.primary_cache_index_key).once
|
167
|
-
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key).once
|
168
|
-
|
169
|
-
@baz.save!
|
170
|
-
@baz.reload
|
171
|
-
end
|
172
|
-
|
173
|
-
assert_equal [@bar.id], Item.fetch(@record.id).cached_associated_record_ids
|
174
|
-
assert_equal [@bar], Item.fetch(@record.id).fetch_associated_records
|
175
|
-
end
|
176
|
-
|
177
|
-
def test_destroying_a_child_record_should_expire_the_parents_cache_blob
|
178
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
179
|
-
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key).once
|
180
|
-
@baz.destroy
|
181
|
-
assert_equal [@bar], @record.reload.fetch_associated_records
|
182
|
-
end
|
183
|
-
|
184
|
-
def test_saving_a_child_record_should_expire_only_itself
|
185
|
-
IdentityCache.cache.expects(:delete).with(@baz.primary_cache_index_key).once
|
186
|
-
@baz.save!
|
187
|
-
end
|
188
|
-
|
189
|
-
def test_touching_child_with_touch_true_on_parent_expires_parent
|
190
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
191
|
-
@not_cached.touch
|
192
|
-
end
|
193
|
-
|
194
|
-
def test_saving_child_with_touch_true_on_parent_expires_parent
|
195
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key).once
|
196
|
-
@not_cached.name = 'Changed'
|
197
|
-
@not_cached.save!
|
198
|
-
end
|
199
|
-
|
200
|
-
def test_fetch_association_does_not_allow_chaining
|
201
|
-
check = proc { assert_equal false, Item.fetch(@record.id).fetch_associated_records.respond_to?(:where) }
|
202
|
-
2.times { check.call } # for miss and hit
|
203
|
-
Item.transaction { check.call }
|
204
|
-
end
|
205
|
-
|
206
|
-
def test_returned_records_should_be_readonly_on_cache_hit
|
207
|
-
IdentityCache.with_fetch_read_only_records do
|
208
|
-
Item.fetch(@record.id) # warm cache
|
209
|
-
record_from_cache_hit = Item.fetch(@record.id)
|
210
|
-
record_from_cache_hit.fetch_associated_records.all?(&:readonly?)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def test_returned_records_should_be_readonly_on_cache_miss
|
215
|
-
IdentityCache.with_fetch_read_only_records do
|
216
|
-
record_from_cache_miss = Item.fetch(@record.id)
|
217
|
-
assert record_from_cache_miss.fetch_associated_records.all?(&:readonly?)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def test_respects_should_use_cache_on_association
|
222
|
-
@record.reload
|
223
|
-
AssociatedRecord.stubs(:should_use_cache?).returns(false)
|
224
|
-
|
225
|
-
assert_queries(1) do
|
226
|
-
assert_memcache_operations(0) do
|
227
|
-
@record.fetch_associated_records
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
@@ -1,379 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PrefetchAssociationsTest < IdentityCache::TestCase
|
4
|
-
NAMESPACE = IdentityCache.cache_namespace
|
5
|
-
|
6
|
-
def setup
|
7
|
-
super
|
8
|
-
@bob = Item.create!(:title => 'bob')
|
9
|
-
@joe = Item.create!(:title => 'joe')
|
10
|
-
@fred = Item.create!(:title => 'fred')
|
11
|
-
@bob_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
12
|
-
@joe_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:2"
|
13
|
-
@fred_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:3"
|
14
|
-
@tenth_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:10"
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_prefetch_associations_on_fetched_records
|
18
|
-
Item.send(:cache_belongs_to, :item)
|
19
|
-
john = Item.create!(:title => 'john')
|
20
|
-
jim = Item.create!(:title => 'jim')
|
21
|
-
@bob.update_column(:item_id, john.id)
|
22
|
-
@joe.update_column(:item_id, jim.id)
|
23
|
-
items = Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
24
|
-
|
25
|
-
spy = Spy.on(Item, :fetch_multi).and_call_through
|
26
|
-
|
27
|
-
Item.prefetch_associations(:item, items)
|
28
|
-
|
29
|
-
assert spy.calls.one?{ |call| call.args == [[john.id, jim.id]] }
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_prefetch_associations_on_db_records
|
33
|
-
Item.send(:cache_has_many, :associated_records, :embed => true)
|
34
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => :ids)
|
35
|
-
|
36
|
-
setup_has_many_children_and_grandchildren(@bob)
|
37
|
-
|
38
|
-
item = Item.find(@bob.id)
|
39
|
-
|
40
|
-
assert_no_queries do
|
41
|
-
assert_memcache_operations(1) do
|
42
|
-
Item.prefetch_associations(:associated_records, [item])
|
43
|
-
end
|
44
|
-
assert_memcache_operations(0) do
|
45
|
-
item.fetch_associated_records.each(&:fetch_deeply_associated_record_ids)
|
46
|
-
end
|
47
|
-
assert_memcache_operations(1) do
|
48
|
-
Item.prefetch_associations({associated_records: :deeply_associated_records}, [item])
|
49
|
-
end
|
50
|
-
assert_memcache_operations(0) do
|
51
|
-
item.fetch_associated_records.each(&:fetch_deeply_associated_records)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_prefetch_associations_without_using_cache
|
57
|
-
Item.send(:cache_has_many, :associated_records, :embed => true)
|
58
|
-
|
59
|
-
associated1 = @bob.associated_records.create!(name: 'foo')
|
60
|
-
associated2 = @joe.associated_records.create!(name: 'bar')
|
61
|
-
items = [@bob, @joe].map(&:reload)
|
62
|
-
|
63
|
-
Item.transaction do
|
64
|
-
assert_memcache_operations(0) do
|
65
|
-
assert_queries(1) do
|
66
|
-
Item.prefetch_associations(:associated_records, items)
|
67
|
-
end
|
68
|
-
assert_no_queries do
|
69
|
-
assert_equal [[associated1], [associated2]], items.map(&:fetch_associated_records)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def test_prefetch_associations_cached_belongs_to
|
76
|
-
Item.send(:cache_belongs_to, :item)
|
77
|
-
@bob.update_attributes!(item_id: @joe.id)
|
78
|
-
@joe.update_attributes!(item_id: @fred.id)
|
79
|
-
@bob.fetch_item
|
80
|
-
@joe.fetch_item
|
81
|
-
items = [@bob, @joe].map(&:reload)
|
82
|
-
|
83
|
-
assert_no_queries do
|
84
|
-
assert_memcache_operations(1) do
|
85
|
-
Item.prefetch_associations(:item, items)
|
86
|
-
end
|
87
|
-
assert_memcache_operations(0) do
|
88
|
-
items.each { |item| item.fetch_item }
|
89
|
-
end
|
90
|
-
assert_memcache_operations(0) do
|
91
|
-
Item.prefetch_associations(:item, items)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def test_prefetch_associations_with_nil_cached_belongs_to
|
97
|
-
Item.send(:cache_belongs_to, :item)
|
98
|
-
@bob.update_attributes!(item_id: 1234)
|
99
|
-
assert_nil @bob.fetch_item
|
100
|
-
|
101
|
-
assert_no_queries do
|
102
|
-
assert_memcache_operations(0) do
|
103
|
-
Item.prefetch_associations(:item, [@bob])
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_prefetch_associations_on_association
|
109
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, embed: true)
|
110
|
-
|
111
|
-
setup_has_many_children_and_grandchildren(@bob)
|
112
|
-
|
113
|
-
associated_records = @bob.associated_records
|
114
|
-
|
115
|
-
assert_queries(1) do
|
116
|
-
assert_memcache_operations(1) do
|
117
|
-
AssociatedRecord.prefetch_associations(:deeply_associated_records, associated_records)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
assert_no_queries do
|
121
|
-
assert_memcache_operations(0) do
|
122
|
-
associated_records.each(&:fetch_deeply_associated_records)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def test_prefetch_associations_through_nil_cache_has_one_association
|
128
|
-
Item.send(:cache_has_one, :associated, embed: true)
|
129
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, embed: :ids)
|
130
|
-
bob_child = @bob.create_associated!(name: "bob child")
|
131
|
-
bob_child.deeply_associated_records.create!(name: "deep child")
|
132
|
-
AssociatedRecord.fetch_multi(bob_child.id)
|
133
|
-
Item.fetch_multi(@bob.id, @joe.id)
|
134
|
-
|
135
|
-
assert_memcache_operations(2) do
|
136
|
-
cached_bob, cached_joe = Item.fetch_multi(@bob.id, @joe.id, includes: {:associated => :deeply_associated_records})
|
137
|
-
assert_nil cached_joe.fetch_associated
|
138
|
-
assert_equal 'deep child', cached_bob.fetch_associated.fetch_deeply_associated_records.first.name
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_fetch_with_includes_option
|
143
|
-
Item.send(:cache_belongs_to, :item)
|
144
|
-
john = Item.create!(title: 'john')
|
145
|
-
@bob.update_column(:item_id, john.id)
|
146
|
-
|
147
|
-
spy = Spy.on(Item, :fetch_multi).and_call_through
|
148
|
-
|
149
|
-
assert_equal @bob, Item.fetch(@bob.id, includes: :item)
|
150
|
-
|
151
|
-
assert spy.calls.one?{ |call| call.args == [[john.id]] }
|
152
|
-
end
|
153
|
-
|
154
|
-
def test_fetch_multi_with_includes_option
|
155
|
-
Item.send(:cache_belongs_to, :item)
|
156
|
-
john = Item.create!(:title => 'john')
|
157
|
-
jim = Item.create!(:title => 'jim')
|
158
|
-
@bob.update_column(:item_id, john.id)
|
159
|
-
@joe.update_column(:item_id, jim.id)
|
160
|
-
|
161
|
-
spy = Spy.on(Item, :fetch_multi).and_call_through
|
162
|
-
|
163
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id, :includes => :item)
|
164
|
-
|
165
|
-
assert spy.calls.one?{ |call| call.args == [[john.id, jim.id]] }
|
166
|
-
end
|
167
|
-
|
168
|
-
def test_fetch_multi_batch_fetches_non_embedded_first_level_has_many_associations
|
169
|
-
Item.send(:cache_has_many, :associated_records, :embed => :ids)
|
170
|
-
|
171
|
-
child_records = []
|
172
|
-
[@bob, @joe].each do |parent|
|
173
|
-
3.times do |i|
|
174
|
-
child_records << (child_record = parent.associated_records.create!(:name => i.to_s))
|
175
|
-
AssociatedRecord.fetch(child_record.id)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
Item.fetch_multi(@bob.id, @joe.id) # populate the cache entries and associated children ID variables
|
180
|
-
|
181
|
-
assert_memcache_operations(2) do
|
182
|
-
@cached_bob, @cached_joe = Item.fetch_multi(@bob.id, @joe.id, :includes => :associated_records)
|
183
|
-
assert_equal child_records[0..2].sort, @cached_bob.fetch_associated_records.sort
|
184
|
-
assert_equal child_records[3..5].sort, @cached_joe.fetch_associated_records.sort
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def test_fetch_multi_batch_fetches_first_level_belongs_to_associations
|
189
|
-
AssociatedRecord.send(:cache_belongs_to, :item, :embed => false)
|
190
|
-
|
191
|
-
@bob_child = @bob.associated_records.create!(:name => "bob child")
|
192
|
-
@fred_child = @fred.associated_records.create!(:name => "fred child")
|
193
|
-
|
194
|
-
# populate the cache entries and associated children ID variables
|
195
|
-
AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id)
|
196
|
-
Item.fetch_multi(@bob.id, @fred.id)
|
197
|
-
|
198
|
-
assert_memcache_operations(2) do
|
199
|
-
@cached_bob_child, @cached_fred_child = AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id, :includes => :item)
|
200
|
-
assert_equal @bob, @cached_bob_child.fetch_item
|
201
|
-
assert_equal @fred, @cached_fred_child.fetch_item
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def test_fetch_multi_batch_fetches_first_level_associations_who_dont_include_identity_cache
|
206
|
-
NotCachedRecord.include(IdentityCache::WithoutPrimaryIndex)
|
207
|
-
Item.send(:cache_has_many, :not_cached_records, :embed => true)
|
208
|
-
|
209
|
-
@bob_child = @bob.not_cached_records.create!(:name => "bob child")
|
210
|
-
@fred_child = @fred.not_cached_records.create!(:name => "fred child")
|
211
|
-
|
212
|
-
# populate the cache entries and associated children ID variables
|
213
|
-
Item.fetch_multi(@bob.id, @fred.id)
|
214
|
-
|
215
|
-
assert_memcache_operations(1) do
|
216
|
-
@cached_bob_child, @cached_fred_child = Item.fetch_multi(@bob.id, @fred.id, :includes => :not_cached_records)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def test_fetch_multi_batch_fetches_non_embedded_second_level_has_many_associations
|
221
|
-
Item.send(:cache_has_many, :associated_records, :embed => :ids)
|
222
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => :ids)
|
223
|
-
|
224
|
-
_child_records, grandchildren = setup_has_many_children_and_grandchildren(@bob, @joe)
|
225
|
-
|
226
|
-
assert_memcache_operations(3) do
|
227
|
-
@cached_bob, @cached_joe = Item.fetch_multi(@bob.id, @joe.id, :includes => {:associated_records => :deeply_associated_records})
|
228
|
-
bob_children = @cached_bob.fetch_associated_records.sort
|
229
|
-
joe_children = @cached_joe.fetch_associated_records.sort
|
230
|
-
|
231
|
-
assert_equal grandchildren[0..2].sort, bob_children[0].fetch_deeply_associated_records.sort
|
232
|
-
assert_equal grandchildren[3..5].sort, bob_children[1].fetch_deeply_associated_records.sort
|
233
|
-
assert_equal grandchildren[6..8].sort, bob_children[2].fetch_deeply_associated_records.sort
|
234
|
-
assert_equal grandchildren[9..11].sort, joe_children[0].fetch_deeply_associated_records.sort
|
235
|
-
assert_equal grandchildren[12..14].sort, joe_children[1].fetch_deeply_associated_records.sort
|
236
|
-
assert_equal grandchildren[15..17].sort, joe_children[2].fetch_deeply_associated_records.sort
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def test_fetch_multi_batch_fetches_non_embedded_second_level_belongs_to_associations
|
241
|
-
Item.send(:cache_belongs_to, :item, :embed => false)
|
242
|
-
AssociatedRecord.send(:cache_belongs_to, :item, :embed => false)
|
243
|
-
|
244
|
-
@bob_child = @bob.associated_records.create!(:name => "bob child")
|
245
|
-
@fred_child = @fred.associated_records.create!(:name => "fred child")
|
246
|
-
@bob.update_attribute(:item_id, @bob.id)
|
247
|
-
@fred.update_attribute(:item_id, @fred.id)
|
248
|
-
|
249
|
-
# populate the cache entries and associated children ID variables
|
250
|
-
AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id)
|
251
|
-
Item.fetch_multi(@bob.id, @fred.id)
|
252
|
-
|
253
|
-
assert_memcache_operations(3) do
|
254
|
-
@cached_bob_child, @cached_fred_child = AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id, :includes => {:item => :item})
|
255
|
-
|
256
|
-
@cached_bob_parent = @cached_bob_child.fetch_item
|
257
|
-
@cached_fred_parent = @cached_fred_child.fetch_item
|
258
|
-
assert_equal @bob, @cached_bob_parent.fetch_item
|
259
|
-
assert_equal @fred, @cached_fred_parent.fetch_item
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def test_fetch_multi_doesnt_batch_fetches_belongs_to_associations_if_the_foreign_key_isnt_present
|
264
|
-
AssociatedRecord.send(:cache_belongs_to, :item, :embed => false)
|
265
|
-
@child = AssociatedRecord.create!(:name => "bob child")
|
266
|
-
# populate the cache entry
|
267
|
-
AssociatedRecord.fetch_multi(@child.id)
|
268
|
-
|
269
|
-
assert_memcache_operations(1) do
|
270
|
-
@cached_child = AssociatedRecord.fetch_multi(@child.id, :includes => :item)
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
|
275
|
-
def test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_many_associations
|
276
|
-
Item.send(:cache_has_many, :associated_records, :embed => true)
|
277
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => :ids)
|
278
|
-
|
279
|
-
_child_records, grandchildren = setup_has_many_children_and_grandchildren(@bob, @joe)
|
280
|
-
|
281
|
-
assert_memcache_operations(2) do
|
282
|
-
@cached_bob, @cached_joe = Item.fetch_multi(@bob.id, @joe.id, :includes => {:associated_records => :deeply_associated_records})
|
283
|
-
bob_children = @cached_bob.fetch_associated_records.sort
|
284
|
-
joe_children = @cached_joe.fetch_associated_records.sort
|
285
|
-
|
286
|
-
assert_equal grandchildren[0..2].sort, bob_children[0].fetch_deeply_associated_records.sort
|
287
|
-
assert_equal grandchildren[3..5].sort, bob_children[1].fetch_deeply_associated_records.sort
|
288
|
-
assert_equal grandchildren[6..8].sort, bob_children[2].fetch_deeply_associated_records.sort
|
289
|
-
assert_equal grandchildren[9..11].sort, joe_children[0].fetch_deeply_associated_records.sort
|
290
|
-
assert_equal grandchildren[12..14].sort, joe_children[1].fetch_deeply_associated_records.sort
|
291
|
-
assert_equal grandchildren[15..17].sort, joe_children[2].fetch_deeply_associated_records.sort
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
def test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_one_associations
|
296
|
-
Item.send(:cache_has_one, :associated, :embed => true)
|
297
|
-
AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => :ids)
|
298
|
-
|
299
|
-
@bob_child = @bob.create_associated!(:name => "bob child")
|
300
|
-
@joe_child = @joe.create_associated!(:name => "joe child")
|
301
|
-
|
302
|
-
grandchildren = setup_grandchildren(@bob_child, @joe_child)
|
303
|
-
AssociatedRecord.fetch_multi(@bob_child.id, @joe_child.id)
|
304
|
-
Item.fetch_multi(@bob.id, @joe.id)
|
305
|
-
|
306
|
-
assert_memcache_operations(2) do
|
307
|
-
@cached_bob, @cached_joe = Item.fetch_multi(@bob.id, @joe.id, :includes => {:associated => :deeply_associated_records})
|
308
|
-
bob_child = @cached_bob.fetch_associated
|
309
|
-
joe_child = @cached_joe.fetch_associated
|
310
|
-
|
311
|
-
assert_equal grandchildren[0..2].sort, bob_child.fetch_deeply_associated_records.sort
|
312
|
-
assert_equal grandchildren[3..5].sort, joe_child.fetch_deeply_associated_records.sort
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
def test_find_batch_coerces_ids_to_primary_key_type
|
317
|
-
mock_relation = mock("ActiveRecord::Relation")
|
318
|
-
Item.expects(:where).returns(mock_relation)
|
319
|
-
mock_relation.expects(:includes).returns(stub(:to_a => [@bob, @joe, @fred]))
|
320
|
-
|
321
|
-
Item.send(:find_batch, [@bob, @joe, @fred].map(&:id).map(&:to_s))
|
322
|
-
end
|
323
|
-
|
324
|
-
def test_fetch_by_index_with_includes_option
|
325
|
-
Item.send(:cache_belongs_to, :item)
|
326
|
-
Item.cache_index :title
|
327
|
-
john = Item.create!(title: 'john')
|
328
|
-
@bob.update_column(:item_id, john.id)
|
329
|
-
|
330
|
-
spy = Spy.on(Item, :fetch_multi).and_call_through
|
331
|
-
|
332
|
-
assert_equal [@bob], Item.fetch_by_title('bob', includes: :item)
|
333
|
-
|
334
|
-
assert spy.calls.one?{ |call| call.args == [[john.id]] }
|
335
|
-
end
|
336
|
-
|
337
|
-
def test_fetch_by_unique_index_with_includes_option
|
338
|
-
Item.send(:cache_belongs_to, :item)
|
339
|
-
Item.cache_index :title, :unique => true
|
340
|
-
john = Item.create!(title: 'john')
|
341
|
-
@bob.update_column(:item_id, john.id)
|
342
|
-
|
343
|
-
spy = Spy.on(Item, :fetch_multi).and_call_through
|
344
|
-
|
345
|
-
assert_equal @bob, Item.fetch_by_title('bob', includes: :item)
|
346
|
-
|
347
|
-
assert spy.calls.one?{ |call| call.args == [[john.id]] }
|
348
|
-
end
|
349
|
-
|
350
|
-
private
|
351
|
-
|
352
|
-
def setup_has_many_children_and_grandchildren(*parents)
|
353
|
-
child_records = []
|
354
|
-
grandchildren = []
|
355
|
-
|
356
|
-
parents.each do |parent|
|
357
|
-
3.times do |i|
|
358
|
-
child_records << (child = parent.associated_records.create!(:name => i.to_s))
|
359
|
-
grandchildren.concat setup_grandchildren(child)
|
360
|
-
AssociatedRecord.fetch(child.id)
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
Item.fetch_multi(*parents.map(&:id)) # populate the cache entries and associated children ID variables
|
365
|
-
|
366
|
-
return child_records, grandchildren
|
367
|
-
end
|
368
|
-
|
369
|
-
def setup_grandchildren(*children)
|
370
|
-
grandchildren = []
|
371
|
-
children.each do |child|
|
372
|
-
3.times do |j|
|
373
|
-
grandchildren << (grandchild = child.deeply_associated_records.create!(:name => j.to_s))
|
374
|
-
DeeplyAssociatedRecord.fetch(grandchild.id)
|
375
|
-
end
|
376
|
-
end
|
377
|
-
grandchildren
|
378
|
-
end
|
379
|
-
end
|