identity_cache 0.5.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec
3
-
4
- gem 'activerecord', '~> 4.2.0'
5
- gem 'activesupport', '~> 4.2.0'
6
- gem 'mysql2', '~> 0.3.13'
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec
3
-
4
- gem 'activerecord', '~> 5.0.0'
5
- gem 'activesupport', '~> 5.0.0'
6
- gem 'mysql2', '>= 0.3.18', '< 0.5'
@@ -1,110 +0,0 @@
1
- require "test_helper"
2
-
3
- class AttributeCacheTest < IdentityCache::TestCase
4
- NAMESPACE = IdentityCache::CacheKeyGeneration::DEFAULT_NAMESPACE
5
-
6
- def setup
7
- super
8
- AssociatedRecord.cache_attribute :name
9
-
10
- @parent = Item.create!(:title => 'bob')
11
- @record = @parent.associated_records.create!(:name => 'foo')
12
- @name_attribute_key = "#{NAMESPACE}attr:AssociatedRecord:name:id:#{cache_hash(@record.id.to_s)}"
13
- IdentityCache.cache.clear
14
- end
15
-
16
- def test_attribute_values_are_fetched_and_returned_on_cache_misses
17
- fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
18
-
19
- assert_queries(1) do
20
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
21
- end
22
- assert fetch.has_been_called_with?(@name_attribute_key)
23
- end
24
-
25
- def test_attribute_values_are_returned_on_cache_hits
26
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
27
-
28
- assert_queries(0) do
29
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
30
- end
31
- end
32
-
33
- def test_nil_is_stored_in_the_cache_on_cache_misses
34
- assert_nil AssociatedRecord.fetch_name_by_id(2)
35
-
36
- assert_queries(0) do
37
- assert_nil AssociatedRecord.fetch_name_by_id(2)
38
- end
39
- end
40
-
41
- def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_is_saved
42
- assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
43
- assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
44
-
45
- @record.save!
46
-
47
- assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
48
- end
49
-
50
- def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_with_changed_attributes_is_saved
51
- assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
52
- assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
53
-
54
- @record.name = 'bar'
55
- @record.save!
56
-
57
- assert_queries(1) { assert_equal 'bar', AssociatedRecord.fetch_name_by_id(1) }
58
- end
59
-
60
- def test_cached_attribute_values_are_expired_from_the_cache_when_an_existing_record_is_destroyed
61
- assert_queries(1) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
62
- assert_queries(0) { assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1) }
63
-
64
- @record.destroy
65
-
66
- assert_queries(1) { assert_nil AssociatedRecord.fetch_name_by_id(1) }
67
- end
68
-
69
- def test_cached_attribute_values_are_expired_from_the_cache_when_a_new_record_is_saved
70
- new_id = 2
71
- assert_queries(1) { assert_nil AssociatedRecord.fetch_name_by_id(new_id) }
72
- assert_queries(0) { assert_nil AssociatedRecord.fetch_name_by_id(new_id) }
73
-
74
- @parent.associated_records.create(:name => 'bar')
75
-
76
- assert_queries(1) { assert_equal 'bar', AssociatedRecord.fetch_name_by_id(new_id) }
77
- end
78
-
79
- def test_fetching_by_attribute_delegates_to_block_if_transactions_are_open
80
- IdentityCache.cache.expects(:read).never
81
-
82
- @record.transaction do
83
- assert_queries(1) do
84
- assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
85
- end
86
- end
87
- end
88
-
89
- def test_previously_stored_cached_nils_are_busted_by_new_record_saves
90
- assert_nil AssociatedRecord.fetch_name_by_id(2)
91
- AssociatedRecord.create(:name => "Jim")
92
- assert_equal "Jim", AssociatedRecord.fetch_name_by_id(2)
93
- end
94
-
95
- def test_cache_attribute_on_derived_model_raises
96
- assert_raises(IdentityCache::DerivedModelError) do
97
- StiRecordTypeA.cache_attribute :name
98
- end
99
- end
100
-
101
- def test_cache_attribute_respects_should_use_cache
102
- AssociatedRecord.stubs(:should_use_cache?).returns(false)
103
-
104
- assert_queries(1) do
105
- assert_memcache_operations(0) do
106
- AssociatedRecord.fetch_name_by_id(@record.id)
107
- end
108
- end
109
- end
110
- end
@@ -1,46 +0,0 @@
1
- require "test_helper"
2
-
3
- class CacheFetchIncludesTest < IdentityCache::TestCase
4
- def setup
5
- super
6
- end
7
-
8
- def test_cached_embedded_has_manys_are_included_in_includes
9
- Item.send(:cache_has_many, :associated_records, :embed => true)
10
- assert_equal [:associated_records], Item.send(:cache_fetch_includes)
11
- end
12
-
13
- def test_cached_nonembedded_has_manys_are_included_in_includes
14
- Item.send(:cache_has_many, :associated_records, :embed => :ids)
15
- assert_equal [], Item.send(:cache_fetch_includes)
16
- end
17
-
18
- def test_cached_has_ones_are_included_in_includes
19
- Item.send(:cache_has_one, :associated)
20
- assert_equal [:associated], Item.send(:cache_fetch_includes)
21
- end
22
-
23
- def test_cached_nonembedded_belongs_tos_are_not_included_in_includes
24
- Item.send(:cache_belongs_to, :item)
25
- assert_equal [], Item.send(:cache_fetch_includes)
26
- end
27
-
28
- def test_cached_child_associations_are_included_in_includes
29
- Item.send(:cache_has_many, :associated_records, :embed => true)
30
- AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => true)
31
- assert_equal [{:associated_records => [:deeply_associated_records]}], Item.send(:cache_fetch_includes)
32
- end
33
-
34
- def test_multiple_cached_associations_and_child_associations_are_included_in_includes
35
- Item.send(:cache_has_many, :associated_records, :embed => true)
36
- PolymorphicRecord.send(:include, IdentityCache::WithoutPrimaryIndex)
37
- Item.send(:cache_has_many, :polymorphic_records, {:inverse_name => :owner, :embed => true})
38
- Item.send(:cache_has_one, :associated, :embed => true)
39
- AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => true)
40
- assert_equal [
41
- {:associated_records => [:deeply_associated_records]},
42
- :polymorphic_records,
43
- {:associated => [:deeply_associated_records]}
44
- ], Item.send(:cache_fetch_includes)
45
- end
46
- end
@@ -1,14 +0,0 @@
1
- require 'test_helper'
2
-
3
- class CacheHashTest < IdentityCache::TestCase
4
-
5
- def test_memcache_hash
6
- 3.times do
7
- random_str = Array.new(200){rand(36).to_s(36)}.join
8
- hash_val = IdentityCache.memcache_hash(random_str)
9
- assert hash_val
10
- assert_kind_of Numeric, hash_val
11
- assert_equal 0, (hash_val >> 64)
12
- end
13
- end
14
- end
@@ -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,214 +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
- IdentityCache.never_set_inverse_association = false
134
- Item.fetch(@record.id) # warm cache
135
-
136
- item = Item.fetch(@record.id)
137
-
138
- associated_record = item.fetch_associated_records.to_a.first
139
- assert_equal item.object_id, associated_record.item.object_id
140
- ensure
141
- IdentityCache.never_set_inverse_association = true
142
- end
143
-
144
- def test_returned_records_should_be_readonly_on_cache_hit
145
- IdentityCache.with_fetch_read_only_records do
146
- Item.fetch(@record.id) # warm cache
147
- record_from_cache_hit = Item.fetch(@record.id)
148
- assert record_from_cache_hit.fetch_associated_records.all?(&:readonly?)
149
- end
150
- end
151
-
152
- def test_returned_records_should_be_readonly_on_cache_miss
153
- IdentityCache.with_fetch_read_only_records do
154
- record_from_cache_miss = Item.fetch(@record.id)
155
- assert record_from_cache_miss.fetch_associated_records.all?(&:readonly?)
156
- end
157
- end
158
-
159
- def test_db_returned_records_should_never_be_readonly
160
- IdentityCache.with_fetch_read_only_records do
161
- record_from_db = Item.find(@record.id)
162
- uncached_records = record_from_db.associated_records
163
- assert uncached_records.none?(&:readonly?)
164
- assert record_from_db.fetch_associated_records.all?(&:readonly?)
165
- assert record_from_db.associated_records.none?(&:readonly?)
166
- end
167
- end
168
-
169
- def test_returned_records_with_open_transactions_should_not_be_readonly
170
- IdentityCache.with_fetch_read_only_records do
171
- Item.transaction do
172
- assert_equal IdentityCache.should_use_cache?, false
173
- assert Item.fetch(@record.id).fetch_associated_records.none?(&:readonly?)
174
- end
175
- end
176
- end
177
-
178
- def test_respect_should_use_cache_from_embedded_records
179
- Item.fetch(@record.id)
180
- AssociatedRecord.stubs(:should_use_cache?).returns(false)
181
-
182
- assert_memcache_operations(1) do
183
- assert_queries(1) do
184
- Item.fetch(@record.id).fetch_associated_records
185
- end
186
- end
187
- end
188
-
189
- class CheckAssociationTest < IdentityCache::TestCase
190
- def test_unsupported_through_assocation
191
- assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
192
- Item.has_many :deeply_through_associated_records, :through => :associated_records, foreign_key: 'associated_record_id', inverse_of: :item, :class_name => 'DeeplyAssociatedRecord'
193
- Item.cache_has_many :deeply_through_associated_records, :embed => true
194
- end
195
- end
196
-
197
- def test_unsupported_joins_in_assocation_scope
198
- scope = -> { joins(:associated_record).where(associated_records: { name: 'contrived example' }) }
199
- Item.has_many :deeply_joined_associated_records, scope, inverse_of: :item, class_name: 'DeeplyAssociatedRecord'
200
- Item.cache_has_many :deeply_joined_associated_records, :embed => true
201
-
202
- message = "caching association Item.deeply_joined_associated_records scoped with a join isn't supported"
203
- assert_raises IdentityCache::UnsupportedAssociationError, message do
204
- Item.fetch(1)
205
- end
206
- end
207
-
208
- def test_cache_has_many_on_derived_model_raises
209
- assert_raises(IdentityCache::DerivedModelError) do
210
- StiRecordTypeA.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
211
- end
212
- end
213
- end
214
- end