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.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/ci.yml +26 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +24 -9
  7. data/CHANGELOG.md +21 -0
  8. data/Gemfile +5 -1
  9. data/README.md +28 -26
  10. data/Rakefile +14 -5
  11. data/dev.yml +9 -16
  12. data/gemfiles/Gemfile.latest-release +6 -0
  13. data/gemfiles/Gemfile.rails-edge +6 -0
  14. data/gemfiles/Gemfile.rails52 +6 -0
  15. data/identity_cache.gemspec +26 -10
  16. data/lib/identity_cache.rb +49 -46
  17. data/lib/identity_cache/belongs_to_caching.rb +12 -40
  18. data/lib/identity_cache/cache_fetcher.rb +6 -5
  19. data/lib/identity_cache/cache_hash.rb +2 -2
  20. data/lib/identity_cache/cache_invalidation.rb +4 -11
  21. data/lib/identity_cache/cache_key_generation.rb +17 -65
  22. data/lib/identity_cache/cache_key_loader.rb +128 -0
  23. data/lib/identity_cache/cached.rb +7 -0
  24. data/lib/identity_cache/cached/association.rb +87 -0
  25. data/lib/identity_cache/cached/attribute.rb +123 -0
  26. data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
  27. data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
  28. data/lib/identity_cache/cached/belongs_to.rb +93 -0
  29. data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
  30. data/lib/identity_cache/cached/prefetcher.rb +51 -0
  31. data/lib/identity_cache/cached/primary_index.rb +97 -0
  32. data/lib/identity_cache/cached/recursive/association.rb +68 -0
  33. data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
  34. data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
  35. data/lib/identity_cache/cached/reference/association.rb +16 -0
  36. data/lib/identity_cache/cached/reference/has_many.rb +105 -0
  37. data/lib/identity_cache/cached/reference/has_one.rb +100 -0
  38. data/lib/identity_cache/configuration_dsl.rb +53 -215
  39. data/lib/identity_cache/encoder.rb +95 -0
  40. data/lib/identity_cache/expiry_hook.rb +36 -0
  41. data/lib/identity_cache/fallback_fetcher.rb +2 -1
  42. data/lib/identity_cache/load_strategy/eager.rb +28 -0
  43. data/lib/identity_cache/load_strategy/lazy.rb +71 -0
  44. data/lib/identity_cache/load_strategy/load_request.rb +20 -0
  45. data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
  46. data/lib/identity_cache/memoized_cache_proxy.rb +127 -58
  47. data/lib/identity_cache/parent_model_expiration.rb +45 -11
  48. data/lib/identity_cache/query_api.rb +128 -394
  49. data/lib/identity_cache/railtie.rb +8 -0
  50. data/lib/identity_cache/record_not_found.rb +6 -0
  51. data/lib/identity_cache/should_use_cache.rb +1 -0
  52. data/lib/identity_cache/version.rb +3 -2
  53. data/lib/identity_cache/with_primary_index.rb +136 -0
  54. data/lib/identity_cache/without_primary_index.rb +24 -3
  55. data/performance/cache_runner.rb +28 -34
  56. data/performance/cpu.rb +3 -2
  57. data/performance/externals.rb +4 -3
  58. data/performance/profile.rb +6 -5
  59. data/railgun.yml +16 -0
  60. metadata +44 -73
  61. data/Gemfile.rails42 +0 -6
  62. data/Gemfile.rails50 +0 -6
  63. data/test/attribute_cache_test.rb +0 -110
  64. data/test/cache_fetch_includes_test.rb +0 -46
  65. data/test/cache_hash_test.rb +0 -14
  66. data/test/cache_invalidation_test.rb +0 -139
  67. data/test/deeply_nested_associated_record_test.rb +0 -19
  68. data/test/denormalized_has_many_test.rb +0 -214
  69. data/test/denormalized_has_one_test.rb +0 -160
  70. data/test/fetch_multi_test.rb +0 -308
  71. data/test/fetch_test.rb +0 -258
  72. data/test/fixtures/serialized_record.mysql2 +0 -0
  73. data/test/fixtures/serialized_record.postgresql +0 -0
  74. data/test/helpers/active_record_objects.rb +0 -106
  75. data/test/helpers/database_connection.rb +0 -72
  76. data/test/helpers/serialization_format.rb +0 -51
  77. data/test/helpers/update_serialization_format.rb +0 -27
  78. data/test/identity_cache_test.rb +0 -29
  79. data/test/index_cache_test.rb +0 -161
  80. data/test/memoized_attributes_test.rb +0 -59
  81. data/test/memoized_cache_proxy_test.rb +0 -107
  82. data/test/normalized_belongs_to_test.rb +0 -107
  83. data/test/normalized_has_many_test.rb +0 -231
  84. data/test/normalized_has_one_test.rb +0 -9
  85. data/test/prefetch_associations_test.rb +0 -379
  86. data/test/readonly_test.rb +0 -109
  87. data/test/recursive_denormalized_has_many_test.rb +0 -131
  88. data/test/save_test.rb +0 -82
  89. data/test/schema_change_test.rb +0 -112
  90. data/test/serialization_format_change_test.rb +0 -16
  91. 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,9 +0,0 @@
1
- require "test_helper"
2
-
3
- class NormalizedHasOneTest < IdentityCache::TestCase
4
- def test_not_implemented_error
5
- assert_raises(NotImplementedError) do
6
- Item.cache_has_one :associated, :embed => false
7
- end
8
- end
9
- 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