identity_cache 0.4.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +92 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/CAVEATS.md +25 -0
- data/CHANGELOG.md +73 -19
- data/Gemfile +5 -1
- data/LICENSE +1 -1
- data/README.md +49 -27
- data/Rakefile +14 -5
- data/dev.yml +12 -16
- data/gemfiles/Gemfile.latest-release +8 -0
- data/gemfiles/Gemfile.min-supported +7 -0
- data/gemfiles/Gemfile.rails-edge +7 -0
- data/identity_cache.gemspec +29 -10
- data/lib/identity_cache.rb +78 -51
- data/lib/identity_cache/belongs_to_caching.rb +12 -40
- data/lib/identity_cache/cache_fetcher.rb +6 -5
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +4 -11
- data/lib/identity_cache/cache_key_generation.rb +17 -65
- data/lib/identity_cache/cache_key_loader.rb +128 -0
- data/lib/identity_cache/cached.rb +7 -0
- data/lib/identity_cache/cached/association.rb +87 -0
- data/lib/identity_cache/cached/attribute.rb +123 -0
- data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
- data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
- data/lib/identity_cache/cached/belongs_to.rb +100 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +61 -0
- data/lib/identity_cache/cached/primary_index.rb +96 -0
- data/lib/identity_cache/cached/recursive/association.rb +109 -0
- data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
- data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
- data/lib/identity_cache/cached/reference/association.rb +16 -0
- data/lib/identity_cache/cached/reference/has_many.rb +105 -0
- data/lib/identity_cache/cached/reference/has_one.rb +100 -0
- data/lib/identity_cache/configuration_dsl.rb +53 -215
- data/lib/identity_cache/encoder.rb +95 -0
- data/lib/identity_cache/expiry_hook.rb +36 -0
- data/lib/identity_cache/fallback_fetcher.rb +2 -1
- data/lib/identity_cache/load_strategy/eager.rb +28 -0
- data/lib/identity_cache/load_strategy/lazy.rb +71 -0
- data/lib/identity_cache/load_strategy/load_request.rb +20 -0
- data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
- data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
- data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
- data/lib/identity_cache/parent_model_expiration.rb +46 -11
- data/lib/identity_cache/query_api.rb +102 -408
- data/lib/identity_cache/railtie.rb +8 -0
- data/lib/identity_cache/record_not_found.rb +6 -0
- data/lib/identity_cache/should_use_cache.rb +1 -0
- data/lib/identity_cache/version.rb +3 -2
- data/lib/identity_cache/with_primary_index.rb +136 -0
- data/lib/identity_cache/without_primary_index.rb +24 -3
- data/performance/cache_runner.rb +25 -73
- data/performance/cpu.rb +4 -3
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +60 -73
- data/.travis.yml +0 -30
- data/Gemfile.rails42 +0 -6
- data/Gemfile.rails50 +0 -6
- data/test/attribute_cache_test.rb +0 -110
- data/test/cache_fetch_includes_test.rb +0 -46
- data/test/cache_hash_test.rb +0 -14
- data/test/cache_invalidation_test.rb +0 -139
- data/test/deeply_nested_associated_record_test.rb +0 -19
- data/test/denormalized_has_many_test.rb +0 -211
- data/test/denormalized_has_one_test.rb +0 -160
- data/test/fetch_multi_test.rb +0 -308
- data/test/fetch_test.rb +0 -258
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +0 -106
- data/test/helpers/database_connection.rb +0 -72
- data/test/helpers/serialization_format.rb +0 -42
- data/test/helpers/update_serialization_format.rb +0 -24
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -49
- data/test/memoized_cache_proxy_test.rb +0 -107
- data/test/normalized_belongs_to_test.rb +0 -107
- data/test/normalized_has_many_test.rb +0 -231
- data/test/normalized_has_one_test.rb +0 -9
- data/test/prefetch_associations_test.rb +0 -364
- data/test/readonly_test.rb +0 -109
- data/test/recursive_denormalized_has_many_test.rb +0 -131
- data/test/save_test.rb +0 -82
- data/test/schema_change_test.rb +0 -112
- data/test/serialization_format_change_test.rb +0 -16
- data/test/test_helper.rb +0 -140
@@ -1,131 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class RecursiveDenormalizedHasManyTest < IdentityCache::TestCase
|
4
|
-
def setup
|
5
|
-
super
|
6
|
-
AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
|
7
|
-
Item.cache_has_many :associated_records, :embed => true
|
8
|
-
Item.cache_has_one :associated
|
9
|
-
|
10
|
-
@record = Item.new(:title => 'foo')
|
11
|
-
|
12
|
-
@associated_record = AssociatedRecord.new(:name => 'bar')
|
13
|
-
@record.associated_records << AssociatedRecord.new(:name => 'baz')
|
14
|
-
@record.associated_records << @associated_record
|
15
|
-
|
16
|
-
@deeply_associated_record = DeeplyAssociatedRecord.new(:name => "corge")
|
17
|
-
@associated_record.deeply_associated_records << @deeply_associated_record
|
18
|
-
@associated_record.deeply_associated_records << DeeplyAssociatedRecord.new(:name => "qux")
|
19
|
-
|
20
|
-
@record.save
|
21
|
-
@record.reload
|
22
|
-
@associated_record.reload
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_cache_fetch_includes
|
26
|
-
assert_equal [{:associated_records => [:deeply_associated_records]}, :associated => [:deeply_associated_records]], Item.send(:cache_fetch_includes)
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_uncached_record_from_the_db_will_use_normal_association_for_deeply_associated_records
|
30
|
-
expected = @associated_record.deeply_associated_records
|
31
|
-
record_from_db = Item.find(@record.id)
|
32
|
-
assert_equal expected, record_from_db.fetch_associated_records[0].fetch_deeply_associated_records
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_on_cache_miss_record_should_embed_associated_objects_and_return
|
36
|
-
record_from_cache_miss = Item.fetch(@record.id)
|
37
|
-
expected = @associated_record.deeply_associated_records
|
38
|
-
|
39
|
-
child_record_from_cache_miss = record_from_cache_miss.fetch_associated_records[0]
|
40
|
-
assert_equal @associated_record, child_record_from_cache_miss
|
41
|
-
assert_equal expected, child_record_from_cache_miss.fetch_deeply_associated_records
|
42
|
-
end
|
43
|
-
|
44
|
-
def test_on_cache_hit_record_should_return_embed_associated_objects
|
45
|
-
Item.fetch(@record.id) # warm cache
|
46
|
-
expected = @associated_record.deeply_associated_records
|
47
|
-
|
48
|
-
Item.any_instance.expects(:associated_records).never
|
49
|
-
AssociatedRecord.any_instance.expects(:deeply_associated_records).never
|
50
|
-
|
51
|
-
record_from_cache_hit = Item.fetch(@record.id)
|
52
|
-
child_record_from_cache_hit = record_from_cache_hit.fetch_associated_records[0]
|
53
|
-
assert_equal @associated_record, child_record_from_cache_hit
|
54
|
-
assert_equal expected, child_record_from_cache_hit.fetch_deeply_associated_records
|
55
|
-
end
|
56
|
-
|
57
|
-
def test_on_cache_miss_child_record_fetch_should_include_nested_associations_to_avoid_n_plus_ones
|
58
|
-
assert_queries(5) do
|
59
|
-
# one for the top level record
|
60
|
-
# one for the mid level has_many association
|
61
|
-
# one for the mid level has_one association
|
62
|
-
# one for the deep level level has_many on the mid level has_many association
|
63
|
-
# one for the deep level level has_many on the mid level has_one association
|
64
|
-
Item.fetch(@record.id)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_saving_child_record_should_expire_parent_record
|
69
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
|
70
|
-
if AssociatedRecord.primary_cache_index_enabled
|
71
|
-
IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key)
|
72
|
-
else
|
73
|
-
IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key).never
|
74
|
-
end
|
75
|
-
@associated_record.name = 'different'
|
76
|
-
@associated_record.save!
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_saving_grand_child_record_should_expire_parent_record
|
80
|
-
IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
|
81
|
-
if AssociatedRecord.primary_cache_index_enabled
|
82
|
-
IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key)
|
83
|
-
else
|
84
|
-
IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key).never
|
85
|
-
end
|
86
|
-
IdentityCache.cache.expects(:delete).with(@deeply_associated_record.primary_cache_index_key)
|
87
|
-
@deeply_associated_record.name = 'different'
|
88
|
-
@deeply_associated_record.save!
|
89
|
-
end
|
90
|
-
|
91
|
-
def test_set_inverse_associations
|
92
|
-
DeeplyAssociatedRecord.cache_belongs_to :associated_record
|
93
|
-
AssociatedRecord.cache_belongs_to :item
|
94
|
-
Item.fetch(@record.id) # warm cache
|
95
|
-
|
96
|
-
item = Item.fetch(@record.id)
|
97
|
-
|
98
|
-
assert_queries(0) do
|
99
|
-
assert_memcache_operations(0) do
|
100
|
-
associated_record = item.fetch_associated_records.to_a.first
|
101
|
-
deeply_associated_record = associated_record.fetch_deeply_associated_records.first
|
102
|
-
assert_equal item.id, deeply_associated_record.fetch_associated_record.fetch_item.id
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
class RecursiveNormalizedHasManyTest < IdentityCache::TestCase
|
110
|
-
def setup
|
111
|
-
super
|
112
|
-
AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
|
113
|
-
Item.cache_has_many :associated_records, :embed => :ids
|
114
|
-
|
115
|
-
@record = Item.new(:title => 'foo')
|
116
|
-
@record.save
|
117
|
-
@record.reload
|
118
|
-
end
|
119
|
-
|
120
|
-
def test_cache_repopulation_should_not_fetch_non_embedded_associations
|
121
|
-
Item.any_instance.expects(:fetch_associated_records).never
|
122
|
-
Item.fetch(@record.id) # cache miss
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
class DisabledPrimaryIndexTest < RecursiveDenormalizedHasManyTest
|
127
|
-
def setup
|
128
|
-
super
|
129
|
-
AssociatedRecord.disable_primary_cache_index
|
130
|
-
end
|
131
|
-
end
|
data/test/save_test.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class SaveTest < IdentityCache::TestCase
|
4
|
-
NAMESPACE = IdentityCache::CacheKeyGeneration::DEFAULT_NAMESPACE
|
5
|
-
|
6
|
-
def setup
|
7
|
-
super
|
8
|
-
Item.cache_index :title, :unique => true
|
9
|
-
Item.cache_index :id, :title, :unique => true
|
10
|
-
|
11
|
-
@record = Item.create(:title => 'bob')
|
12
|
-
@blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_create
|
16
|
-
@record = Item.new
|
17
|
-
@record.title = 'bob'
|
18
|
-
|
19
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('2/bob')}")
|
20
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
21
|
-
expect_cache_delete("#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:2").once
|
22
|
-
@record.save
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_update
|
26
|
-
# Regular flow, write index id, write index id/tile, delete data blob since Record has changed
|
27
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/fred')}")
|
28
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('fred')}")
|
29
|
-
expect_cache_delete(@blob_key)
|
30
|
-
|
31
|
-
# Delete index id, delete index id/title
|
32
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/bob')}")
|
33
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
34
|
-
|
35
|
-
@record.title = 'fred'
|
36
|
-
@record.save
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_destroy
|
40
|
-
# Regular flow: delete data blob, delete index id, delete index id/tile
|
41
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/bob')}")
|
42
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
43
|
-
expect_cache_delete(@blob_key)
|
44
|
-
|
45
|
-
@record.destroy
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_destroy_with_changed_attributes
|
49
|
-
# Make sure to delete the old cache index key, since the new title never ended up in an index
|
50
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/bob')}")
|
51
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
52
|
-
expect_cache_delete(@blob_key)
|
53
|
-
|
54
|
-
@record.title = 'fred'
|
55
|
-
@record.destroy
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_touch_will_expire_the_caches
|
59
|
-
# Regular flow: delete data blob, delete index id, delete index id/tile
|
60
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/bob')}")
|
61
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
62
|
-
expect_cache_delete(@blob_key)
|
63
|
-
|
64
|
-
@record.touch
|
65
|
-
end
|
66
|
-
|
67
|
-
def test_expire_cache_works_in_a_transaction
|
68
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:id/title:#{cache_hash('1/bob')}")
|
69
|
-
expect_cache_delete("#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}")
|
70
|
-
expect_cache_delete(@blob_key)
|
71
|
-
|
72
|
-
ActiveRecord::Base.transaction do
|
73
|
-
@record.send(:expire_cache)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def expect_cache_delete(key)
|
80
|
-
@backend.expects(:write).with(key, IdentityCache::DELETED, anything)
|
81
|
-
end
|
82
|
-
end
|
data/test/schema_change_test.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class SchemaChangeTest < IdentityCache::TestCase
|
4
|
-
class AddColumnToChild < ActiveRecord::Migration
|
5
|
-
def up
|
6
|
-
add_column :associated_records, :shiny, :string
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class AddColumnToDeepChild < ActiveRecord::Migration
|
11
|
-
def up
|
12
|
-
add_column :deeply_associated_records, :new_column, :string
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def setup
|
17
|
-
super
|
18
|
-
ActiveRecord::Migration.verbose = false
|
19
|
-
|
20
|
-
read_new_schema
|
21
|
-
Item.cache_has_one :associated, :embed => true
|
22
|
-
AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
|
23
|
-
|
24
|
-
@associated_record = AssociatedRecord.new(:name => 'bar')
|
25
|
-
@deeply_associated_record = DeeplyAssociatedRecord.new(:name => "corge")
|
26
|
-
@associated_record.deeply_associated_records << @deeply_associated_record
|
27
|
-
@associated_record.deeply_associated_records << DeeplyAssociatedRecord.new(:name => "qux")
|
28
|
-
@record = Item.new(:title => 'foo')
|
29
|
-
@record.associated = @associated_record
|
30
|
-
|
31
|
-
@associated_record.save!
|
32
|
-
@record.save!
|
33
|
-
|
34
|
-
@record.reload
|
35
|
-
end
|
36
|
-
|
37
|
-
def teardown
|
38
|
-
active_records = [AssociatedRecord, DeeplyAssociatedRecord]
|
39
|
-
super
|
40
|
-
active_records.each {|ar| ar.reset_column_information }
|
41
|
-
end
|
42
|
-
|
43
|
-
# This helper simulates the models being reloaded
|
44
|
-
def read_new_schema
|
45
|
-
AssociatedRecord.reset_column_information
|
46
|
-
DeeplyAssociatedRecord.reset_column_information
|
47
|
-
|
48
|
-
AssociatedRecord.send(:instance_variable_set, :@rails_cache_key_prefix, nil)
|
49
|
-
Item.send(:instance_variable_set, :@rails_cache_key_prefix, nil)
|
50
|
-
end
|
51
|
-
|
52
|
-
def test_schema_changes_on_embedded_association_should_cause_cache_miss_for_old_cached_objects
|
53
|
-
record = Item.fetch(@record.id)
|
54
|
-
record.fetch_associated
|
55
|
-
|
56
|
-
AddColumnToChild.new.up
|
57
|
-
read_new_schema
|
58
|
-
|
59
|
-
Item.expects(:resolve_cache_miss).returns(@record)
|
60
|
-
record = Item.fetch(@record.id)
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_schema_changes_on_deeply_embedded_association_should_cause_cache_miss_for_old_cached_objects
|
64
|
-
record = Item.fetch(@record.id)
|
65
|
-
associated_record_from_cache = record.fetch_associated
|
66
|
-
associated_record_from_cache.fetch_deeply_associated_records
|
67
|
-
|
68
|
-
AddColumnToDeepChild.new.up
|
69
|
-
read_new_schema
|
70
|
-
|
71
|
-
Item.expects(:resolve_cache_miss).returns(@record)
|
72
|
-
record = Item.fetch(@record.id)
|
73
|
-
end
|
74
|
-
|
75
|
-
def test_schema_changes_on_new_cached_child_association
|
76
|
-
record = Item.fetch(@record.id)
|
77
|
-
|
78
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
79
|
-
Item.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
80
|
-
read_new_schema
|
81
|
-
|
82
|
-
Item.expects(:resolve_cache_miss).returns(@record)
|
83
|
-
record = Item.fetch(@record.id)
|
84
|
-
end
|
85
|
-
|
86
|
-
def test_embed_existing_cache_has_many
|
87
|
-
PolymorphicRecord.include(IdentityCache)
|
88
|
-
Item.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => :ids
|
89
|
-
read_new_schema
|
90
|
-
|
91
|
-
record = Item.fetch(@record.id)
|
92
|
-
|
93
|
-
teardown_models
|
94
|
-
setup_models
|
95
|
-
|
96
|
-
PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
|
97
|
-
Item.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => true
|
98
|
-
read_new_schema
|
99
|
-
|
100
|
-
record = Item.fetch(@record.id)
|
101
|
-
end
|
102
|
-
|
103
|
-
def test_add_non_embedded_cache_has_many
|
104
|
-
PolymorphicRecord.include(IdentityCache)
|
105
|
-
record = Item.fetch(@record.id)
|
106
|
-
|
107
|
-
Item.cache_has_many :polymorphic_records, :inverse_name => :owner, :embed => :ids
|
108
|
-
read_new_schema
|
109
|
-
|
110
|
-
record = Item.fetch(@record.id)
|
111
|
-
end
|
112
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "helpers/serialization_format"
|
3
|
-
|
4
|
-
class SerializationFormatChangeTest < IdentityCache::TestCase
|
5
|
-
include SerializationFormat
|
6
|
-
|
7
|
-
MESSAGE = "serialization format changed => increment IdentityCache.CACHE_VERSION and run rake update_serialization_format"
|
8
|
-
|
9
|
-
def test_serialization_format_has_not_changed
|
10
|
-
serialization = Marshal.load(serialize(serialized_record))
|
11
|
-
preserialization = Marshal.load(File.binread(serialized_record_file))
|
12
|
-
assert_equal(preserialization, serialization, MESSAGE)
|
13
|
-
rescue SystemCallError
|
14
|
-
assert(false, MESSAGE)
|
15
|
-
end
|
16
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'mocha/setup'
|
4
|
-
require 'active_record'
|
5
|
-
require 'helpers/database_connection'
|
6
|
-
require 'helpers/active_record_objects'
|
7
|
-
require 'spy/integration'
|
8
|
-
require 'memcached_store'
|
9
|
-
require 'active_support/cache/memcached_store'
|
10
|
-
|
11
|
-
require File.dirname(__FILE__) + '/../lib/identity_cache'
|
12
|
-
|
13
|
-
DatabaseConnection.setup
|
14
|
-
ActiveSupport::Cache::Store.instrument = true if ActiveSupport.version < Gem::Version.new("4.2.0")
|
15
|
-
|
16
|
-
# This patches AR::MemcacheStore to notify AS::Notifications upon read_multis like the rest of rails does
|
17
|
-
module MemcachedStoreInstrumentation
|
18
|
-
def read_multi(*args, &block)
|
19
|
-
instrument('read_multi', 'MULTI', keys: args) do
|
20
|
-
super(*args, &block)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
ActiveSupport::Cache::MemcachedStore.prepend(MemcachedStoreInstrumentation)
|
25
|
-
|
26
|
-
|
27
|
-
MiniTest::Test = MiniTest::Unit::TestCase unless defined?(MiniTest::Test)
|
28
|
-
class IdentityCache::TestCase < Minitest::Test
|
29
|
-
include ActiveRecordObjects
|
30
|
-
attr_reader :backend
|
31
|
-
|
32
|
-
def setup
|
33
|
-
if ActiveRecord.gem_version < Gem::Version.new('5') && ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
|
34
|
-
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
35
|
-
end
|
36
|
-
|
37
|
-
DatabaseConnection.drop_tables
|
38
|
-
DatabaseConnection.create_tables
|
39
|
-
|
40
|
-
IdentityCache.logger = Logger.new(nil)
|
41
|
-
|
42
|
-
memcached_host = ENV['MEMCACHED_HOST'] || "127.0.0.1"
|
43
|
-
IdentityCache.cache_backend = @backend = ActiveSupport::Cache::MemcachedStore.new("#{memcached_host}:11211", :support_cas => true)
|
44
|
-
|
45
|
-
setup_models
|
46
|
-
end
|
47
|
-
|
48
|
-
def teardown
|
49
|
-
IdentityCache.cache.clear
|
50
|
-
teardown_models
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def fetcher
|
56
|
-
IdentityCache.cache.cache_fetcher
|
57
|
-
end
|
58
|
-
|
59
|
-
def assert_nothing_raised
|
60
|
-
yield
|
61
|
-
end
|
62
|
-
|
63
|
-
def assert_not_nil(*args)
|
64
|
-
assert(*args)
|
65
|
-
end
|
66
|
-
|
67
|
-
def assert_queries(num = 1)
|
68
|
-
counter = SQLCounter.new
|
69
|
-
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record', counter)
|
70
|
-
exception = false
|
71
|
-
yield
|
72
|
-
rescue
|
73
|
-
exception = true
|
74
|
-
raise
|
75
|
-
ensure
|
76
|
-
ActiveSupport::Notifications.unsubscribe(subscriber)
|
77
|
-
assert_equal num, counter.log.size, "#{counter.log.size} instead of #{num} queries were executed.#{counter.log.size == 0 ? '' : "\nQueries:\n#{counter.log.join("\n")}"}" unless exception
|
78
|
-
end
|
79
|
-
|
80
|
-
def assert_memcache_operations(num)
|
81
|
-
counter = CacheCounter.new
|
82
|
-
subscriber = ActiveSupport::Notifications.subscribe(/cache_.*\.active_support/, counter)
|
83
|
-
exception = false
|
84
|
-
yield
|
85
|
-
rescue
|
86
|
-
exception = true
|
87
|
-
raise
|
88
|
-
ensure
|
89
|
-
ActiveSupport::Notifications.unsubscribe(subscriber)
|
90
|
-
assert_equal num, counter.log.size, "#{counter.log.size} instead of #{num} memcache operations were executed. #{counter.log.size == 0 ? '' : "\nOperations:\n#{counter.log.join("\n")}"}" unless exception
|
91
|
-
end
|
92
|
-
|
93
|
-
def assert_no_queries
|
94
|
-
assert_queries(0) do
|
95
|
-
yield
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def cache_hash(key)
|
100
|
-
IdentityCache.memcache_hash(key)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
class SQLCounter
|
105
|
-
cattr_accessor :ignored_sql
|
106
|
-
self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/, /^SHOW /]
|
107
|
-
|
108
|
-
# FIXME: this needs to be refactored so specific database can add their own
|
109
|
-
# ignored SQL. This ignored SQL is for Oracle.
|
110
|
-
ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
|
111
|
-
|
112
|
-
attr_reader :ignore
|
113
|
-
attr_accessor :log
|
114
|
-
|
115
|
-
def initialize(ignore = self.class.ignored_sql)
|
116
|
-
@ignore = ignore
|
117
|
-
@log = []
|
118
|
-
end
|
119
|
-
|
120
|
-
def call(name, start, finish, message_id, values)
|
121
|
-
sql = values[:sql]
|
122
|
-
|
123
|
-
# FIXME: this seems bad. we should probably have a better way to indicate
|
124
|
-
# the query was cached
|
125
|
-
return if 'CACHE' == values[:name] || ignore.any? { |x| x =~ sql }
|
126
|
-
self.log << sql
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
class CacheCounter
|
131
|
-
attr_accessor :log
|
132
|
-
|
133
|
-
def initialize()
|
134
|
-
@log = []
|
135
|
-
end
|
136
|
-
|
137
|
-
def call(name, start, finish, message_id, values)
|
138
|
-
self.log << "#{name} #{(values[:keys].try(:join, ', ') || values[:key])}"
|
139
|
-
end
|
140
|
-
end
|