identity_cache 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,123 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require 'active_record'
3
+ require 'active_support/core_ext'
4
+ require 'active_support/cache'
5
+ require 'identity_cache'
6
+ require 'memcache'
7
+
8
+ if ENV['BOXEN_HOME'].present?
9
+ $memcached_port = 21211
10
+ $mysql_port = 13306
11
+ else
12
+ $memcached_port = 11211
13
+ $mysql_port = 3306
14
+ end
15
+
16
+ require File.dirname(__FILE__) + '/../test/helpers/active_record_objects'
17
+ require File.dirname(__FILE__) + '/../test/helpers/database_connection'
18
+ require File.dirname(__FILE__) + '/../test/helpers/cache'
19
+
20
+ def create_record(id)
21
+ r = Record.new(id)
22
+ end
23
+
24
+ def database_ready(count)
25
+ Record.where(:id => (1..count)).count == count
26
+ rescue
27
+ false
28
+ end
29
+
30
+ def create_database(count)
31
+ DatabaseConnection.setup
32
+ a = CacheRunner.new(count)
33
+
34
+ a.setup_models
35
+
36
+ DatabaseConnection.setup
37
+ # set up associations
38
+ Record.cache_has_one :associated
39
+ Record.cache_has_many :associated_records, :embed => true
40
+ Record.cache_index :title, :unique => :true
41
+ AssociatedRecord.cache_has_many :deeply_associated_records, :embed => true
42
+
43
+ return if database_ready(count)
44
+ puts "Database not ready for performance testing, generating records"
45
+
46
+ DatabaseConnection.drop_tables
47
+ DatabaseConnection.create_tables
48
+ existing = Record.all
49
+ (1..count).to_a.each do |i|
50
+ unless existing.any? { |e| e.id == i }
51
+ a = Record.new
52
+ a.id = i
53
+ a.associated = AssociatedRecord.new(name: "Associated for #{i}")
54
+ a.associated_records
55
+ (1..5).each do |j|
56
+ a.associated_records << AssociatedRecord.new(name: "Has Many #{j} for #{i}")
57
+ end
58
+ a.save
59
+ end
60
+ end
61
+ end
62
+
63
+ class CacheRunner
64
+ include ActiveRecordObjects
65
+ include DatabaseConnection
66
+
67
+ def initialize(count)
68
+ @count = count
69
+ end
70
+
71
+ def prepare
72
+ end
73
+ end
74
+
75
+ class FindRunner < CacheRunner
76
+ def run
77
+ i = 1
78
+ @count.times do
79
+ ::Record.find(i)
80
+ i+=1
81
+ end
82
+ end
83
+ end
84
+
85
+ class FetchMissRunner < CacheRunner
86
+ def prepare
87
+ IdentityCache.cache.clear
88
+ end
89
+
90
+ def run
91
+ i = 1
92
+ @count.times do
93
+ rec = ::Record.fetch(i)
94
+ rec.fetch_associated
95
+ rec.fetch_associated_records
96
+
97
+ i+=1
98
+ end
99
+ end
100
+ end
101
+
102
+ class FetchHitRunner < CacheRunner
103
+ def prepare
104
+ IdentityCache.cache.clear
105
+ i = 1
106
+ @count.times do
107
+ ::Record.fetch(i)
108
+ i+=1
109
+ end
110
+ end
111
+
112
+ def run
113
+ i = 1
114
+ @count.times do
115
+ rec = ::Record.fetch(i)
116
+ # these should all be no cost
117
+ rec.fetch_associated
118
+ rec.fetch_associated_records
119
+ i+=1
120
+ end
121
+ end
122
+ end
123
+
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+
4
+ require_relative 'cache_runner'
5
+
6
+ RUNS = 4000
7
+
8
+ class ARCreator
9
+ include ActiveRecordObjects
10
+ end
11
+
12
+ def run(obj, bench)
13
+ bench.report("#{obj.class.name}:") do
14
+ obj.prepare
15
+ obj.run
16
+ end
17
+ end
18
+
19
+ create_database(RUNS)
20
+
21
+ Benchmark.bmbm do |x|
22
+ run(FindRunner.new(RUNS), x)
23
+
24
+ run(FetchMissRunner.new(RUNS), x)
25
+
26
+ run(FetchHitRunner.new(RUNS), x)
27
+
28
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+ require 'ruby-prof'
4
+
5
+ require_relative 'cache_runner'
6
+
7
+ RUNS = 1000
8
+ RubyProf.measure_mode = RubyProf::CPU_TIME
9
+
10
+ EXTERNALS = {"Memcache" => ["MemCache#set", "MemCache#get"],
11
+ "Database" => ["Mysql2::Client#query"]}
12
+
13
+ def run(obj)
14
+ obj.prepare
15
+ RubyProf.start
16
+ obj.run
17
+ result = RubyProf.stop
18
+ puts "Results for #{obj.class.name}:"
19
+ results = StringIO.new
20
+ printer = RubyProf::FlatPrinter.new(result)
21
+ printer.print(results)
22
+ count_externals(results.string)
23
+ end
24
+
25
+ def count_externals(results)
26
+ count = {}
27
+ results.split(/\n/).each do |line|
28
+ fields = line.split
29
+ if ext = EXTERNALS.detect { |e| e[1].any? { |method| method == fields[-1] } }
30
+ count[ext[0]] ||= 0
31
+ count[ext[0]] += fields[-2].to_i
32
+ end
33
+ end
34
+ EXTERNALS.each do |ext|
35
+ puts "#{ext[0]}: #{count[ext[0]] || 0}"
36
+ end
37
+ end
38
+
39
+ create_database(RUNS)
40
+
41
+ run(FindRunner.new(RUNS))
42
+
43
+ run(FetchHitRunner.new(RUNS))
44
+
45
+ run(FetchMissRunner.new(RUNS))
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+ require 'ruby-prof'
4
+
5
+ require_relative 'cache_runner'
6
+
7
+ RUNS = 1000
8
+ RubyProf.measure_mode = RubyProf::CPU_TIME
9
+
10
+ def run(obj)
11
+ obj.prepare
12
+ RubyProf.start
13
+ obj.run
14
+ result = RubyProf.stop
15
+ puts "Results for #{obj.class.name}:"
16
+ printer = RubyProf::FlatPrinter.new(result)
17
+ printer.print(STDOUT)
18
+ end
19
+
20
+ create_database(RUNS)
21
+
22
+ run(FindRunner.new(RUNS))
23
+
24
+ run(FetchMissRunner.new(RUNS))
25
+
26
+ run(FetchHitRunner.new(RUNS))
@@ -19,7 +19,7 @@ class AttributeCacheTest < IdentityCache::TestCase
19
19
 
20
20
  def test_attribute_values_are_fetched_and_returned_on_cache_misses
21
21
  IdentityCache.cache.expects(:read).with(@name_attribute_key).returns(nil)
22
- Record.connection.expects(:select_value).with("SELECT name FROM associated_records WHERE id = 1 LIMIT 1").returns('foo')
22
+ Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
23
23
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ class AttributeCacheTest < IdentityCache::TestCase
29
29
  IdentityCache.cache.expects(:read).with(@name_attribute_key).returns(nil)
30
30
 
31
31
  # Grab the value of the attribute from the DB
32
- Record.connection.expects(:select_value).with("SELECT name FROM associated_records WHERE id = 1 LIMIT 1").returns('foo')
32
+ Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
33
33
 
34
34
  # And write it back to the cache
35
35
  IdentityCache.cache.expects(:write).with(@name_attribute_key, 'foo').returns(nil)
@@ -64,7 +64,7 @@ class AttributeCacheTest < IdentityCache::TestCase
64
64
  def test_fetching_by_attribute_delegates_to_block_if_transactions_are_open
65
65
  IdentityCache.cache.expects(:read).with(@name_attribute_key).never
66
66
 
67
- Record.connection.expects(:select_value).with("SELECT name FROM associated_records WHERE id = 1 LIMIT 1").returns('foo')
67
+ Record.connection.expects(:select_value).with("SELECT `name` FROM `associated_records` WHERE `id` = 1 LIMIT 1").returns('foo')
68
68
 
69
69
  @record.transaction do
70
70
  assert_equal 'foo', AssociatedRecord.fetch_name_by_id(1)
@@ -6,10 +6,10 @@ class FetchMultiTest < IdentityCache::TestCase
6
6
  @bob = Record.create!(:title => 'bob')
7
7
  @joe = Record.create!(:title => 'joe')
8
8
  @fred = Record.create!(:title => 'fred')
9
- @bob_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:1"
10
- @joe_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:2"
11
- @fred_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:3"
12
- @tenth_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:10"
9
+ @bob_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:1"
10
+ @joe_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:2"
11
+ @fred_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:3"
12
+ @tenth_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:10"
13
13
  end
14
14
 
15
15
  def test_fetch_multi_with_no_records
@@ -107,48 +107,22 @@ class FetchMultiTest < IdentityCache::TestCase
107
107
  assert_equal [@joe, @bob, @joe], Record.fetch_multi(@joe.id, @bob.id, @joe.id)
108
108
  end
109
109
 
110
- def test_fetch_multi_includes_cached_associations
111
- Record.send(:cache_has_many, :associated_records, :embed => true)
112
- Record.send(:cache_has_one, :associated)
113
- Record.send(:cache_belongs_to, :record)
114
-
115
- cache_response = {}
116
- cache_response[@bob_blob_key] = nil
117
- cache_response[@joe_blob_key] = nil
118
- cache_response[@fred_blob_key] = nil
119
-
120
- IdentityCache.cache.expects(:read_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
121
-
110
+ def test_find_batch_coerces_ids_to_primary_key_type
122
111
  mock_relation = mock("ActiveRecord::Relation")
123
112
  Record.expects(:where).returns(mock_relation)
124
- mock_relation.expects(:includes).with([:associated_records, :associated]).returns(stub(:all => [@bob, @joe, @fred]))
125
- assert_equal [@bob, @joe, @fred], Record.fetch_multi(@bob.id, @joe.id, @fred.id)
126
- end
113
+ mock_relation.expects(:includes).returns(stub(:all => [@bob, @joe, @fred]))
127
114
 
128
- def test_fetch_multi_includes_cached_associations_and_other_asked_for_associations
129
- Record.send(:cache_has_many, :associated_records, :embed => true)
130
- Record.send(:cache_has_one, :associated)
131
- Record.send(:cache_belongs_to, :record)
115
+ Record.find_batch([@bob, @joe, @fred].map(&:id).map(&:to_s))
116
+ end
132
117
 
118
+ def test_fetch_multi_doesnt_freeze_keys
133
119
  cache_response = {}
134
- cache_response[@bob_blob_key] = nil
135
- cache_response[@joe_blob_key] = nil
136
- cache_response[@fred_blob_key] = nil
137
-
138
- IdentityCache.cache.expects(:read_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
139
-
140
- mock_relation = mock("ActiveRecord::Relation")
141
- Record.expects(:where).returns(mock_relation)
142
- mock_relation.expects(:includes).with([:associated_records, :associated, {:record => []}]).returns(stub(:all => [@bob, @joe, @fred]))
143
- assert_equal [@bob, @joe, @fred], Record.fetch_multi(@bob.id, @joe.id, @fred.id, {:includes => :record})
144
- end
120
+ cache_response[@bob_blob_key] = @bob
121
+ cache_response[@joe_blob_key] = @fred
145
122
 
146
- def test_find_batch_coerces_ids_to_primary_key_type
147
- mock_relation = mock("ActiveRecord::Relation")
148
- Record.expects(:where).returns(mock_relation)
149
- mock_relation.expects(:includes).returns(stub(:all => [@bob, @joe, @fred]))
123
+ IdentityCache.expects(:fetch_multi).with{ |*args| args.none?(&:frozen?) }.returns(cache_response)
150
124
 
151
- Record.find_batch([@bob, @joe, @fred].map(&:id).map(&:to_s))
125
+ Record.fetch_multi(@bob.id, @joe.id)
152
126
  end
153
127
 
154
128
  private
@@ -0,0 +1,236 @@
1
+ require "test_helper"
2
+
3
+ class FetchMultiTest < IdentityCache::TestCase
4
+ def setup
5
+ super
6
+ @bob = Record.create!(:title => 'bob')
7
+ @joe = Record.create!(:title => 'joe')
8
+ @fred = Record.create!(:title => 'fred')
9
+ @bob_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:1"
10
+ @joe_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:2"
11
+ @fred_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:3"
12
+ @tenth_blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:10"
13
+ end
14
+
15
+ def test_fetch_multi_includes_cached_associations_in_the_database_find
16
+ Record.send(:cache_has_many, :associated_records, :embed => true)
17
+ Record.send(:cache_has_one, :associated)
18
+ Record.send(:cache_belongs_to, :record)
19
+
20
+ cache_response = {}
21
+ cache_response[@bob_blob_key] = nil
22
+ cache_response[@joe_blob_key] = nil
23
+ cache_response[@fred_blob_key] = nil
24
+
25
+ IdentityCache.cache.expects(:read_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
26
+
27
+ mock_relation = mock("ActiveRecord::Relation")
28
+ Record.expects(:where).returns(mock_relation)
29
+ mock_relation.expects(:includes).with([:associated_records, :associated]).returns(stub(:all => [@bob, @joe, @fred]))
30
+ assert_equal [@bob, @joe, @fred], Record.fetch_multi(@bob.id, @joe.id, @fred.id)
31
+ end
32
+
33
+ def test_fetch_multi_includes_cached_associations_and_other_asked_for_associations_in_the_database_find
34
+ Record.send(:cache_has_many, :associated_records, :embed => true)
35
+ Record.send(:cache_has_one, :associated)
36
+ Record.send(:cache_belongs_to, :record)
37
+
38
+ cache_response = {}
39
+ cache_response[@bob_blob_key] = nil
40
+ cache_response[@joe_blob_key] = nil
41
+ cache_response[@fred_blob_key] = nil
42
+
43
+ IdentityCache.cache.expects(:read_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
44
+
45
+ mock_relation = mock("ActiveRecord::Relation")
46
+ Record.expects(:where).returns(mock_relation)
47
+ mock_relation.expects(:includes).with([:associated_records, :associated, {:record => []}]).returns(stub(:all => [@bob, @joe, @fred]))
48
+ assert_equal [@bob, @joe, @fred], Record.fetch_multi(@bob.id, @joe.id, @fred.id, {:includes => :record})
49
+ end
50
+
51
+ def test_fetch_multi_batch_fetches_non_embedded_first_level_has_many_associations
52
+ Record.send(:cache_has_many, :associated_records, :embed => false)
53
+
54
+ child_records = []
55
+ [@bob, @joe].each do |parent|
56
+ 3.times do |i|
57
+ child_records << (child_record = parent.associated_records.create!(:name => i.to_s))
58
+ AssociatedRecord.fetch(child_record.id)
59
+ end
60
+ end
61
+
62
+ Record.fetch_multi(@bob.id, @joe.id) # populate the cache entries and associated children ID variables
63
+
64
+ assert_memcache_operations(2) do
65
+ @cached_bob, @cached_joe = Record.fetch_multi(@bob.id, @joe.id, :includes => :associated_records)
66
+ assert_equal child_records[0..2].sort, @cached_bob.fetch_associated_records.sort
67
+ assert_equal child_records[3..5].sort, @cached_joe.fetch_associated_records.sort
68
+ end
69
+ end
70
+
71
+ def test_fetch_multi_batch_fetches_first_level_belongs_to_associations
72
+ AssociatedRecord.send(:cache_belongs_to, :record, :embed => false)
73
+
74
+ @bob_child = @bob.associated_records.create!(:name => "bob child")
75
+ @fred_child = @fred.associated_records.create!(:name => "fred child")
76
+
77
+ # populate the cache entries and associated children ID variables
78
+ AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id)
79
+ Record.fetch_multi(@bob.id, @fred.id)
80
+
81
+ assert_memcache_operations(2) do
82
+ @cached_bob_child, @cached_fred_child = AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id, :includes => :record)
83
+ assert_equal @bob, @cached_bob_child.fetch_record
84
+ assert_equal @fred, @cached_fred_child.fetch_record
85
+ end
86
+ end
87
+
88
+ def test_fetch_multi_batch_fetches_first_level_associations_who_dont_include_identity_cache
89
+ Record.send(:has_many, :not_cached_records)
90
+ Record.send(:cache_has_many, :not_cached_records, :embed => true)
91
+
92
+ @bob_child = @bob.not_cached_records.create!(:name => "bob child")
93
+ @fred_child = @fred.not_cached_records.create!(:name => "fred child")
94
+
95
+ # populate the cache entries and associated children ID variables
96
+ Record.fetch_multi(@bob.id, @fred.id)
97
+
98
+ assert_memcache_operations(1) do
99
+ @cached_bob_child, @cached_fred_child = Record.fetch_multi(@bob.id, @fred.id, :includes => :not_cached_records)
100
+ end
101
+ end
102
+
103
+ def test_fetch_multi_batch_fetches_non_embedded_second_level_has_many_associations
104
+ Record.send(:cache_has_many, :associated_records, :embed => false)
105
+ AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => false)
106
+
107
+ child_records, grandchildren = setup_has_many_children_and_grandchildren(@bob, @joe)
108
+
109
+ assert_memcache_operations(3) do
110
+ @cached_bob, @cached_joe = Record.fetch_multi(@bob.id, @joe.id, :includes => {:associated_records => :deeply_associated_records})
111
+ bob_children = @cached_bob.fetch_associated_records.sort
112
+ joe_children = @cached_joe.fetch_associated_records.sort
113
+
114
+ assert_equal grandchildren[0..2].sort, bob_children[0].fetch_deeply_associated_records.sort
115
+ assert_equal grandchildren[3..5].sort, bob_children[1].fetch_deeply_associated_records.sort
116
+ assert_equal grandchildren[6..8].sort, bob_children[2].fetch_deeply_associated_records.sort
117
+ assert_equal grandchildren[9..11].sort, joe_children[0].fetch_deeply_associated_records.sort
118
+ assert_equal grandchildren[12..14].sort, joe_children[1].fetch_deeply_associated_records.sort
119
+ assert_equal grandchildren[15..17].sort, joe_children[2].fetch_deeply_associated_records.sort
120
+ end
121
+ end
122
+
123
+ def test_fetch_multi_batch_fetches_non_embedded_second_level_belongs_to_associations
124
+ Record.send(:cache_belongs_to, :record, :embed => false)
125
+ AssociatedRecord.send(:cache_belongs_to, :record, :embed => false)
126
+
127
+ @bob_child = @bob.associated_records.create!(:name => "bob child")
128
+ @fred_child = @fred.associated_records.create!(:name => "fred child")
129
+ @bob.update_attribute(:record_id, @bob.id)
130
+ @fred.update_attribute(:record_id, @fred.id)
131
+
132
+ # populate the cache entries and associated children ID variables
133
+ AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id)
134
+ Record.fetch_multi(@bob.id, @fred.id)
135
+
136
+ assert_memcache_operations(3) do
137
+ @cached_bob_child, @cached_fred_child = AssociatedRecord.fetch_multi(@bob_child.id, @fred_child.id, :includes => {:record => :record})
138
+
139
+ @cached_bob_parent = @cached_bob_child.fetch_record
140
+ @cached_fred_parent = @cached_fred_child.fetch_record
141
+ assert_equal @bob, @cached_bob_parent.fetch_record
142
+ assert_equal @fred, @cached_fred_parent.fetch_record
143
+ end
144
+ end
145
+
146
+ def test_fetch_multi_doesnt_batch_fetches_belongs_to_associations_if_the_foreign_key_isnt_present
147
+ AssociatedRecord.send(:cache_belongs_to, :record, :embed => false)
148
+ @child = AssociatedRecord.create!(:name => "bob child")
149
+ # populate the cache entry
150
+ AssociatedRecord.fetch_multi(@child.id)
151
+
152
+ assert_memcache_operations(1) do
153
+ @cached_child = AssociatedRecord.fetch_multi(@child.id, :includes => :record)
154
+ end
155
+ end
156
+
157
+
158
+ def test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_many_associations
159
+ Record.send(:cache_has_many, :associated_records, :embed => true)
160
+ AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => false)
161
+
162
+ child_records, grandchildren = setup_has_many_children_and_grandchildren(@bob, @joe)
163
+
164
+ assert_memcache_operations(2) do
165
+ @cached_bob, @cached_joe = Record.fetch_multi(@bob.id, @joe.id, :includes => {:associated_records => :deeply_associated_records})
166
+ bob_children = @cached_bob.fetch_associated_records.sort
167
+ joe_children = @cached_joe.fetch_associated_records.sort
168
+
169
+ assert_equal grandchildren[0..2].sort, bob_children[0].fetch_deeply_associated_records.sort
170
+ assert_equal grandchildren[3..5].sort, bob_children[1].fetch_deeply_associated_records.sort
171
+ assert_equal grandchildren[6..8].sort, bob_children[2].fetch_deeply_associated_records.sort
172
+ assert_equal grandchildren[9..11].sort, joe_children[0].fetch_deeply_associated_records.sort
173
+ assert_equal grandchildren[12..14].sort, joe_children[1].fetch_deeply_associated_records.sort
174
+ assert_equal grandchildren[15..17].sort, joe_children[2].fetch_deeply_associated_records.sort
175
+ end
176
+ end
177
+
178
+ def test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_one_associations
179
+ Record.send(:cache_has_one, :associated, :embed => true)
180
+ AssociatedRecord.send(:cache_has_many, :deeply_associated_records, :embed => false)
181
+
182
+ @bob_child = @bob.create_associated!(:name => "bob child")
183
+ @joe_child = @joe.create_associated!(:name => "joe child")
184
+
185
+ grandchildren = setup_grandchildren(@bob_child, @joe_child)
186
+ AssociatedRecord.fetch_multi(@bob_child.id, @joe_child.id)
187
+ Record.fetch_multi(@bob.id, @joe.id)
188
+
189
+ assert_memcache_operations(2) do
190
+ @cached_bob, @cached_joe = Record.fetch_multi(@bob.id, @joe.id, :includes => {:associated => :deeply_associated_records})
191
+ bob_child = @cached_bob.fetch_associated
192
+ joe_child = @cached_joe.fetch_associated
193
+
194
+ assert_equal grandchildren[0..2].sort, bob_child.fetch_deeply_associated_records.sort
195
+ assert_equal grandchildren[3..5].sort, joe_child.fetch_deeply_associated_records.sort
196
+ end
197
+ end
198
+
199
+ def test_find_batch_coerces_ids_to_primary_key_type
200
+ mock_relation = mock("ActiveRecord::Relation")
201
+ Record.expects(:where).returns(mock_relation)
202
+ mock_relation.expects(:includes).returns(stub(:all => [@bob, @joe, @fred]))
203
+
204
+ Record.find_batch([@bob, @joe, @fred].map(&:id).map(&:to_s))
205
+ end
206
+
207
+ private
208
+
209
+ def setup_has_many_children_and_grandchildren(*parents)
210
+ child_records = []
211
+ grandchildren = []
212
+
213
+ parents.each do |parent|
214
+ 3.times do |i|
215
+ child_records << (child = parent.associated_records.create!(:name => i.to_s))
216
+ grandchildren.concat setup_grandchildren(child)
217
+ AssociatedRecord.fetch(child.id)
218
+ end
219
+ end
220
+
221
+ Record.fetch_multi(*parents.map(&:id)) # populate the cache entries and associated children ID variables
222
+
223
+ return child_records, grandchildren
224
+ end
225
+
226
+ def setup_grandchildren(*children)
227
+ grandchildren = []
228
+ children.each do |child|
229
+ 3.times do |j|
230
+ grandchildren << (grandchild = child.deeply_associated_records.create!(:name => j.to_s))
231
+ DeeplyAssociatedRecord.fetch(grandchild.id)
232
+ end
233
+ end
234
+ grandchildren
235
+ end
236
+ end
data/test/fetch_test.rb CHANGED
@@ -9,7 +9,7 @@ class FetchTest < IdentityCache::TestCase
9
9
  @record = Record.new
10
10
  @record.id = 1
11
11
  @record.title = 'bob'
12
- @blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,title:string,updated_at:datetime")}:1"
12
+ @blob_key = "IDC:blob:Record:#{cache_hash("created_at:datetime,id:integer,record_id:integer,title:string,updated_at:datetime")}:1"
13
13
  @index_key = "IDC:index:Record:title:#{cache_hash('bob')}"
14
14
  end
15
15
 
@@ -0,0 +1,43 @@
1
+ module ActiveRecordObjects
2
+ def setup_models(base = ActiveRecord::Base)
3
+ Object.send :const_set, 'DeeplyAssociatedRecord', Class.new(base).tap {|klass|
4
+ klass.send :include, IdentityCache
5
+ klass.belongs_to :associated_record
6
+ }
7
+
8
+ Object.send :const_set, 'AssociatedRecord', Class.new(base).tap {|klass|
9
+ klass.send :include, IdentityCache
10
+ klass.belongs_to :record
11
+ klass.has_many :deeply_associated_records, :order => "name DESC"
12
+ }
13
+
14
+ Object.send :const_set, 'NotCachedRecord', Class.new(base).tap {|klass|
15
+ klass.belongs_to :record, :touch => true
16
+ }
17
+
18
+ Object.send :const_set, 'PolymorphicRecord', Class.new(base).tap {|klass|
19
+ klass.belongs_to :owner, :polymorphic => true
20
+ }
21
+
22
+ Object.send :const_set, 'Record', Class.new(base).tap {|klass|
23
+ klass.send :include, IdentityCache
24
+ klass.belongs_to :record
25
+ klass.has_many :associated_records, :order => "id DESC"
26
+ klass.has_many :not_cached_records, :order => "id DESC"
27
+ klass.has_many :polymorphic_records, :as => 'owner'
28
+ klass.has_one :polymorphic_record, :as => 'owner'
29
+ klass.has_one :associated, :class_name => 'AssociatedRecord', :order => "id ASC"
30
+ }
31
+ end
32
+
33
+ def teardown_models
34
+ ActiveSupport::DescendantsTracker.clear
35
+ ActiveSupport::Dependencies.clear
36
+ Object.send :remove_const, 'DeeplyAssociatedRecord'
37
+ Object.send :remove_const, 'PolymorphicRecord'
38
+ Object.send :remove_const, 'AssociatedRecord'
39
+ Object.send :remove_const, 'NotCachedRecord'
40
+ Object.send :remove_const, 'Record'
41
+ end
42
+ end
43
+
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module Rails
2
4
 
3
5
  class Cache < ActiveSupport::Cache::MemCacheStore
@@ -7,18 +9,7 @@ module Rails
7
9
  @@cache ||= Cache.new("localhost:#{$memcached_port}")
8
10
  end
9
11
 
10
- class Logger
11
- def info(string)
12
- end
13
-
14
- def debug(string)
15
- end
16
-
17
- def error(string)
18
- end
19
- end
20
-
21
12
  def self.logger
22
- @logger = Logger.new
13
+ @logger ||= Logger.new(nil)
23
14
  end
24
15
  end
@@ -30,7 +30,8 @@ module DatabaseConnection
30
30
  :deeply_associated_records => [[:string, :name], [:integer, :associated_record_id]],
31
31
  :associated_records => [[:string, :name], [:integer, :record_id]],
32
32
  :not_cached_records => [[:string, :name], [:integer, :record_id]],
33
- :records => [[:string, :title], [:timestamps]]
33
+ :records => [[:integer, :record_id], [:string, :title], [:timestamps]],
34
+ :records2 => [[:integer, :record_id], [:string, :title], [:timestamps]]
34
35
  }
35
36
 
36
37
  DATABASE_CONFIG = {
@@ -93,4 +93,11 @@ class ExpirationTest < IdentityCache::TestCase
93
93
  assert_equal nil, IdentityCache.cache.read(@cache_key)
94
94
  end
95
95
 
96
+ def test_set_table_name_cache_fetch
97
+ Record.cache_index :title
98
+ Record.table_name = 'records2'
99
+ @record.save!
100
+ assert_equal [@record], Record.fetch_by_title('bob')
101
+ assert_equal [@record.id], IdentityCache.cache.read(@cache_key)
102
+ end
96
103
  end