identity_cache 0.0.1

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.
@@ -0,0 +1,97 @@
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
+ Record.cache_has_many :associated_records, :embed => true
8
+ Record.cache_has_one :associated
9
+
10
+ @record = Record.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]], Record.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 = Record.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 = Record.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
+ Record.fetch(@record.id) # warm cache
46
+ expected = @associated_record.deeply_associated_records
47
+
48
+ Record.any_instance.expects(:associated_records).never
49
+ AssociatedRecord.any_instance.expects(:deeply_associated_records).never
50
+
51
+ record_from_cache_hit = Record.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
+ record_from_cache_miss = Record.fetch(@record.id)
59
+ expected = @associated_record.deeply_associated_records
60
+
61
+ assert record_from_cache_miss.fetch_associated_records[0].deeply_associated_records.loaded?
62
+ assert record_from_cache_miss.fetch_associated_records[1].deeply_associated_records.loaded?
63
+ end
64
+
65
+ def test_saving_child_record_should_expire_parent_record
66
+ IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
67
+ IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key)
68
+ @associated_record.name = 'different'
69
+ @associated_record.save!
70
+ end
71
+
72
+ def test_saving_grand_child_record_should_expire_parent_record
73
+ IdentityCache.cache.expects(:delete).with(@record.primary_cache_index_key)
74
+ IdentityCache.cache.expects(:delete).with(@associated_record.primary_cache_index_key)
75
+ IdentityCache.cache.expects(:delete).with(@deeply_associated_record.primary_cache_index_key)
76
+ @deeply_associated_record.name = 'different'
77
+ @deeply_associated_record.save!
78
+ end
79
+
80
+ end
81
+
82
+ class RecursiveNormalizedHasManyTest < IdentityCache::TestCase
83
+ def setup
84
+ super
85
+ AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
86
+ Record.cache_has_many :associated_records, :embed => false
87
+
88
+ @record = Record.new(:title => 'foo')
89
+ @record.save
90
+ @record.reload
91
+ end
92
+
93
+ def test_cache_repopulation_should_not_fetch_non_embedded_associations
94
+ Record.any_instance.expects(:fetch_associated_records).never
95
+ record_from_cache_miss = Record.fetch(@record.id)
96
+ end
97
+ end
data/test/save_test.rb ADDED
@@ -0,0 +1,64 @@
1
+ require "test_helper"
2
+
3
+ class SaveTest < IdentityCache::TestCase
4
+ def setup
5
+ super
6
+ Record.cache_index :title, :unique => true
7
+ Record.cache_index :id, :title, :unique => true
8
+
9
+ @record = Record.create(:title => 'bob')
10
+ @blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:1"
11
+ end
12
+
13
+ def test_create
14
+ @record = Record.new
15
+ @record.title = 'bob'
16
+
17
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('2/bob')}")
18
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('bob')}")
19
+ IdentityCache.cache.expects(:delete).with("IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:2").once
20
+ @record.save
21
+ end
22
+
23
+ def test_update
24
+ # Regular flow, write index id, write index id/tile, delete data blob since Record has changed
25
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('1/fred')}")
26
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('fred')}")
27
+ IdentityCache.cache.expects(:delete).with(@blob_key)
28
+
29
+ # Delete index id, delete index id/title
30
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('1/bob')}")
31
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('bob')}")
32
+
33
+ @record.title = 'fred'
34
+ @record.save
35
+ end
36
+
37
+ def test_destroy
38
+ # Regular flow: delete data blob, delete index id, delete index id/tile
39
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('1/bob')}")
40
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('bob')}")
41
+ IdentityCache.cache.expects(:delete).with(@blob_key)
42
+
43
+ @record.destroy
44
+ end
45
+
46
+ def test_destroy_with_changed_attributes
47
+ # Make sure to delete the old cache index key, since the new title never ended up in an index
48
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('1/bob')}")
49
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('bob')}")
50
+ IdentityCache.cache.expects(:delete).with(@blob_key)
51
+
52
+ @record.title = 'fred'
53
+ @record.destroy
54
+ end
55
+
56
+ def test_touch_will_expire_the_caches
57
+ # Regular flow: delete data blob, delete index id, delete index id/tile
58
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:id/title:#{cache_hash('1/bob')}")
59
+ IdentityCache.cache.expects(:delete).with("IDC:index:Record:title:#{cache_hash('bob')}")
60
+ IdentityCache.cache.expects(:delete).with(@blob_key)
61
+
62
+ @record.touch
63
+ end
64
+ end
@@ -0,0 +1,73 @@
1
+ require 'minitest/autorun'
2
+ require 'mocha/setup'
3
+ require 'active_record'
4
+ require 'helpers/cache'
5
+ require 'helpers/database_connection'
6
+
7
+ require File.dirname(__FILE__) + '/../lib/identity_cache'
8
+
9
+ DatabaseConnection.setup
10
+
11
+ class IdentityCache::TestCase < MiniTest::Unit::TestCase
12
+ def setup
13
+ DatabaseConnection.drop_tables
14
+ DatabaseConnection.create_tables
15
+
16
+ setup_models
17
+ end
18
+
19
+ def teardown
20
+ IdentityCache.cache.clear
21
+ ActiveSupport::DescendantsTracker.clear
22
+ ActiveSupport::Dependencies.clear
23
+ Object.send :remove_const, 'DeeplyAssociatedRecord'
24
+ Object.send :remove_const, 'PolymorphicRecord'
25
+ Object.send :remove_const, 'AssociatedRecord'
26
+ Object.send :remove_const, 'NotCachedRecord'
27
+ Object.send :remove_const, 'Record'
28
+ end
29
+
30
+ def assert_nothing_raised
31
+ yield
32
+ end
33
+
34
+ def assert_not_nil(*args)
35
+ assert *args
36
+ end
37
+
38
+ def cache_hash(key)
39
+ CityHash.hash64(key)
40
+ end
41
+
42
+ private
43
+ def setup_models
44
+ Object.send :const_set, 'DeeplyAssociatedRecord', Class.new(ActiveRecord::Base).tap {|klass|
45
+ klass.send :include, IdentityCache
46
+ klass.belongs_to :associated_record
47
+ }
48
+
49
+ Object.send :const_set, 'AssociatedRecord', Class.new(ActiveRecord::Base).tap {|klass|
50
+ klass.send :include, IdentityCache
51
+ klass.belongs_to :record
52
+ klass.has_many :deeply_associated_records, :order => "name DESC"
53
+ }
54
+
55
+ Object.send :const_set, 'NotCachedRecord', Class.new(ActiveRecord::Base).tap {|klass|
56
+ klass.belongs_to :record, :touch => true
57
+ }
58
+
59
+ Object.send :const_set, 'PolymorphicRecord', Class.new(ActiveRecord::Base).tap {|klass|
60
+ klass.belongs_to :owner, :polymorphic => true
61
+ }
62
+
63
+ Object.send :const_set, 'Record', Class.new(ActiveRecord::Base).tap {|klass|
64
+ klass.send :include, IdentityCache
65
+ klass.belongs_to :record
66
+ klass.has_many :associated_records, :order => "id DESC"
67
+ klass.has_many :not_cached_records, :order => "id DESC"
68
+ klass.has_many :polymorphic_records, :as => 'owner'
69
+ klass.has_one :polymorphic_record, :as => 'owner'
70
+ klass.has_one :associated, :class_name => 'AssociatedRecord', :order => "id ASC"
71
+ }
72
+ end
73
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: identity_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Camilo Lopez
9
+ - Tom Burns
10
+ - Harry Brundage
11
+ - Dylan Smith
12
+ - Tobias Lütke
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2013-03-17 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: ar_transaction_changes
20
+ requirement: &70221938344660 !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - =
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.1
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: *70221938344660
29
+ - !ruby/object:Gem::Dependency
30
+ name: cityhash
31
+ requirement: &70221938344160 !ruby/object:Gem::Requirement
32
+ none: false
33
+ requirements:
34
+ - - =
35
+ - !ruby/object:Gem::Version
36
+ version: 0.6.0
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: *70221938344160
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: &70221938343780 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: *70221938343780
51
+ - !ruby/object:Gem::Dependency
52
+ name: mocha
53
+ requirement: &70221938343320 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: *70221938343320
62
+ - !ruby/object:Gem::Dependency
63
+ name: mysql2
64
+ requirement: &70221938342900 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: *70221938342900
73
+ description: Opt in read through ActiveRecord caching.
74
+ email:
75
+ - harry.brundage@shopify.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - .gitignore
81
+ - .travis.yml
82
+ - Gemfile
83
+ - LICENSE
84
+ - README.md
85
+ - Rakefile
86
+ - identity_cache.gemspec
87
+ - lib/belongs_to_caching.rb
88
+ - lib/identity_cache.rb
89
+ - lib/identity_cache/version.rb
90
+ - lib/memoized_cache_proxy.rb
91
+ - test/attribute_cache_test.rb
92
+ - test/denormalized_has_many_test.rb
93
+ - test/denormalized_has_one_test.rb
94
+ - test/fetch_multi_test.rb
95
+ - test/fetch_test.rb
96
+ - test/helpers/cache.rb
97
+ - test/helpers/database_connection.rb
98
+ - test/identity_cache_test.rb
99
+ - test/index_cache_test.rb
100
+ - test/memoized_cache_proxy_test.rb
101
+ - test/normalized_belongs_to_test.rb
102
+ - test/normalized_has_many_test.rb
103
+ - test/recursive_denormalized_has_many_test.rb
104
+ - test/save_test.rb
105
+ - test/test_helper.rb
106
+ homepage: https://github.com/Shopify/identity_cache
107
+ licenses: []
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: 460007319304896635
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ segments:
128
+ - 0
129
+ hash: 460007319304896635
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 1.8.11
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: IdentityCache lets you specify how you want to cache your model objects,
136
+ at the model level, and adds a number of convenience methods for accessing those
137
+ objects through the cache. Memcached is used as the backend cache store, and the
138
+ database is only hit when a copy of the object cannot be found in Memcached.
139
+ test_files:
140
+ - test/attribute_cache_test.rb
141
+ - test/denormalized_has_many_test.rb
142
+ - test/denormalized_has_one_test.rb
143
+ - test/fetch_multi_test.rb
144
+ - test/fetch_test.rb
145
+ - test/helpers/cache.rb
146
+ - test/helpers/database_connection.rb
147
+ - test/identity_cache_test.rb
148
+ - test/index_cache_test.rb
149
+ - test/memoized_cache_proxy_test.rb
150
+ - test/normalized_belongs_to_test.rb
151
+ - test/normalized_has_many_test.rb
152
+ - test/recursive_denormalized_has_many_test.rb
153
+ - test/save_test.rb
154
+ - test/test_helper.rb