identity_cache 0.0.2 → 0.0.3

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,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