active_record_shards 2.0.0.beta1
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.
- data/README.rdoc +33 -0
- data/lib/active_record_shards.rb +13 -0
- data/lib/active_record_shards/association_collection_connection_selection.rb +25 -0
- data/lib/active_record_shards/configuration_parser.rb +46 -0
- data/lib/active_record_shards/connection_pool.rb +29 -0
- data/lib/active_record_shards/connection_switcher.rb +104 -0
- data/lib/active_record_shards/model.rb +20 -0
- data/lib/active_record_shards/shard_selection.rb +41 -0
- data/test/configuration_parser_test.rb +88 -0
- data/test/connection_switching_test.rb +167 -0
- data/test/database.yml +54 -0
- data/test/database_parse_test.yml +21 -0
- data/test/helper.rb +57 -0
- data/test/models.rb +11 -0
- data/test/schema.rb +16 -0
- data/test/test.log +64898 -0
- metadata +181 -0
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= active_record_shards
|
2
|
+
|
3
|
+
== Example
|
4
|
+
|
5
|
+
Ticket.on_slave do
|
6
|
+
Ticket.first(50)
|
7
|
+
end
|
8
|
+
|
9
|
+
== Install
|
10
|
+
|
11
|
+
gem install active_record_shards
|
12
|
+
|
13
|
+
Add the slave database to config/database.yml:
|
14
|
+
development_slave:
|
15
|
+
adapter: mysql
|
16
|
+
...
|
17
|
+
|
18
|
+
== Note on Patches/Pull Requests
|
19
|
+
|
20
|
+
* Fork the project.
|
21
|
+
* Make your feature addition or bug fix.
|
22
|
+
* Add tests for it. This is important so I don't break it in a
|
23
|
+
future version unintentionally.
|
24
|
+
* Commit, do not mess with rakefile, version, or history.
|
25
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
26
|
+
* Send me a pull request. Bonus points for topic branches.
|
27
|
+
|
28
|
+
== Copyright
|
29
|
+
|
30
|
+
Copyright (c) 2009 Zendesk. See LICENSE for details.
|
31
|
+
|
32
|
+
== Authors
|
33
|
+
Mick Staugaard
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/base'
|
3
|
+
require 'active_record_shards/configuration_parser'
|
4
|
+
require 'active_record_shards/model'
|
5
|
+
require 'active_record_shards/shard_selection'
|
6
|
+
require 'active_record_shards/connection_switcher'
|
7
|
+
require 'active_record_shards/association_collection_connection_selection'
|
8
|
+
require 'active_record_shards/connection_pool'
|
9
|
+
|
10
|
+
ActiveRecord::Base.extend(ActiveRecordShards::ConfigurationParser)
|
11
|
+
ActiveRecord::Base.extend(ActiveRecordShards::Model)
|
12
|
+
ActiveRecord::Base.extend(ActiveRecordShards::ConnectionSwitcher)
|
13
|
+
ActiveRecord::Associations::AssociationCollection.send(:include, ActiveRecordShards::AssociationCollectionConnectionSelection)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
module AssociationCollectionConnectionSelection
|
3
|
+
def on_slave_if(condition)
|
4
|
+
condition ? on_slave : self
|
5
|
+
end
|
6
|
+
|
7
|
+
def on_slave_unless(condition)
|
8
|
+
on_slave_if(!condition)
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_slave
|
12
|
+
SlaveProxy.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
class SlaveProxy
|
16
|
+
def initialize(association_collection)
|
17
|
+
@association_collection = association_collection
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
@association_collection.proxy_reflection.klass.on_slave_block { @association_collection.send(method, *args, &block) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
module ConfigurationParser
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def explode(conf)
|
6
|
+
conf.keys.each do |env_name|
|
7
|
+
env_config = conf[env_name]
|
8
|
+
if shards = env_config.delete('shards')
|
9
|
+
shards.each do |shard_name, shard_conf|
|
10
|
+
expand_child!(env_config, shard_conf)
|
11
|
+
conf["#{env_name}_shard_#{shard_name}"] = shard_conf
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
conf.keys.each do |env_name|
|
17
|
+
env_config = conf[env_name]
|
18
|
+
if slave_conf = env_config.delete('slave')
|
19
|
+
expand_child!(env_config, slave_conf)
|
20
|
+
conf["#{env_name}_slave"] = slave_conf
|
21
|
+
end
|
22
|
+
end
|
23
|
+
conf
|
24
|
+
end
|
25
|
+
|
26
|
+
def expand_child!(parent, child)
|
27
|
+
parent.each do |key, value|
|
28
|
+
unless ['slave', 'shards'].include?(key) || value.is_a?(Hash)
|
29
|
+
child[key] ||= value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def configurations_with_shard_explosion=(conf)
|
35
|
+
self.configurations_without_shard_explosion = explode(conf)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ConfigurationParser.extended(klass)
|
39
|
+
klass.singleton_class.alias_method_chain :configurations=, :shard_explosion
|
40
|
+
|
41
|
+
if !klass.configurations.nil? && !klass.configurations.empty?
|
42
|
+
klass.configurations = klass.configurations
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
|
2
|
+
# The only difference here is that we use klass.connection_pool_name
|
3
|
+
# instead of klass.name as the pool key
|
4
|
+
def retrieve_connection_pool(klass)
|
5
|
+
pool = @connection_pools[klass.connection_pool_name]
|
6
|
+
return pool if pool
|
7
|
+
return nil if ActiveRecord::Base == klass
|
8
|
+
retrieve_connection_pool klass.superclass
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_connection(klass)
|
12
|
+
pool = @connection_pools[klass.connection_pool_name]
|
13
|
+
@connection_pools.delete_if { |key, value| value == pool }
|
14
|
+
pool.disconnect! if pool
|
15
|
+
pool.spec.config if pool
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.singleton_class.class_eval do
|
20
|
+
def establish_connection_with_connection_pool_name(spec = nil)
|
21
|
+
case spec
|
22
|
+
when ActiveRecord::Base::ConnectionSpecification
|
23
|
+
connection_handler.establish_connection(connection_pool_name, spec)
|
24
|
+
else
|
25
|
+
establish_connection_without_connection_pool_name(spec)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method_chain :establish_connection, :connection_pool_name
|
29
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
module ConnectionSwitcher
|
3
|
+
|
4
|
+
def on_shard(shard, &block)
|
5
|
+
old_shard = current_shard_selection.shard
|
6
|
+
switch_connection(:shard => shard)
|
7
|
+
yield
|
8
|
+
ensure
|
9
|
+
switch_connection(:shard => old_shard)
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_slave_if(condition, &block)
|
13
|
+
condition ? on_slave(&block) : yield
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_slave_unless(condition, &block)
|
17
|
+
on_slave_if(!condition, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes queries using the slave database. Fails over to master if no slave is found.
|
21
|
+
# if you want to execute a block of code on the slave you can go:
|
22
|
+
# Account.on_slave do
|
23
|
+
# Account.first
|
24
|
+
# end
|
25
|
+
# the first account will be found on the slave DB
|
26
|
+
#
|
27
|
+
# For one-liners you can simply do
|
28
|
+
# Account.on_slave.first
|
29
|
+
def on_slave(&block)
|
30
|
+
if block_given?
|
31
|
+
on_slave_block(&block)
|
32
|
+
else
|
33
|
+
SlaveProxy.new(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_slave_block(&block)
|
38
|
+
old_slave = current_shard_selection.on_slave?
|
39
|
+
begin
|
40
|
+
switch_connection(:slave => true)
|
41
|
+
rescue ActiveRecord::AdapterNotSpecified => e
|
42
|
+
switch_connection(:slave => old_slave)
|
43
|
+
logger.warn("Failed to establish shard connection: #{e.message} - defaulting to master")
|
44
|
+
end
|
45
|
+
with_scope({:find => {:readonly => current_shard_selection.on_slave?}}, :merge, &block)
|
46
|
+
ensure
|
47
|
+
switch_connection(:slave => old_slave)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Name of the connection pool. Used by ConnectionHandler to retrieve the current connection pool.
|
51
|
+
def connection_pool_name # :nodoc:
|
52
|
+
current_shard_selection.shard_name(self)
|
53
|
+
# if current_shard_selection.any?
|
54
|
+
# current_shard_selection.shard_name(name, self)
|
55
|
+
# elsif self == ActiveRecord::Base
|
56
|
+
# name
|
57
|
+
# else
|
58
|
+
# superclass.connection_pool_name
|
59
|
+
# end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def current_shard_selection
|
65
|
+
Thread.current[:shard_selection] ||= ShardSelection.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def switch_connection(options)
|
69
|
+
if options.any?
|
70
|
+
if options.has_key?(:slave)
|
71
|
+
current_shard_selection.on_slave = options[:slave]
|
72
|
+
end
|
73
|
+
|
74
|
+
if options.has_key?(:shard)
|
75
|
+
current_shard_selection.shard = options[:shard]
|
76
|
+
end
|
77
|
+
|
78
|
+
establish_shard_connection unless connected_to_shard?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def establish_shard_connection
|
83
|
+
name = current_shard_selection.shard_name(self)
|
84
|
+
spec = configurations[name]
|
85
|
+
raise ActiveRecord::AdapterNotSpecified.new("No database defined by #{name} in database.yml") if spec.nil?
|
86
|
+
|
87
|
+
connection_handler.establish_connection(connection_pool_name, ActiveRecord::Base::ConnectionSpecification.new(spec, "#{spec['adapter']}_connection"))
|
88
|
+
end
|
89
|
+
|
90
|
+
def connected_to_shard?
|
91
|
+
connection_handler.connection_pools.has_key?(connection_pool_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
class SlaveProxy
|
95
|
+
def initialize(target)
|
96
|
+
@target = target
|
97
|
+
end
|
98
|
+
|
99
|
+
def method_missing(method, *args, &block)
|
100
|
+
@target.on_slave_block { @target.send(method, *args, &block) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
module Model
|
3
|
+
def not_sharded
|
4
|
+
if self == ActiveRecord::Base || self != base_class
|
5
|
+
raise "You should only call not_sharded on direct descendants of ActiveRecord::Base"
|
6
|
+
end
|
7
|
+
@sharded = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_sharded?
|
11
|
+
if self == ActiveRecord::Base
|
12
|
+
true
|
13
|
+
elsif self == base_class
|
14
|
+
@sharded != false
|
15
|
+
else
|
16
|
+
base_class.is_sharded?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
class ShardSelection
|
3
|
+
def initialize
|
4
|
+
@on_slave = false
|
5
|
+
end
|
6
|
+
|
7
|
+
def shard
|
8
|
+
@shard
|
9
|
+
end
|
10
|
+
|
11
|
+
def shard=(new_shard)
|
12
|
+
@shard = new_shard
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_slave?
|
16
|
+
@on_slave
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_slave=(new_slave)
|
20
|
+
@on_slave = (new_slave == true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection_configuration_name
|
24
|
+
shard_name#(RAILS_ENV)
|
25
|
+
end
|
26
|
+
|
27
|
+
def shard_name(klass = nil)
|
28
|
+
s = "#{RAILS_ENV}"
|
29
|
+
if @shard && (klass.nil? || klass.is_sharded?)
|
30
|
+
s << '_shard_'
|
31
|
+
s << @shard.to_s
|
32
|
+
end
|
33
|
+
s << "_slave" if @on_slave
|
34
|
+
s
|
35
|
+
end
|
36
|
+
|
37
|
+
def any?
|
38
|
+
@on_slave || @shard.present?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path('helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class ConfigurationParserTest < ActiveSupport::TestCase
|
4
|
+
context "exploding the database.yml" do
|
5
|
+
setup do
|
6
|
+
@exploded_conf = ActiveRecordShards::ConfigurationParser.explode(YAML::load(IO.read(File.dirname(__FILE__) + '/database_parse_test.yml')))
|
7
|
+
end
|
8
|
+
|
9
|
+
context "main slave" do
|
10
|
+
setup { @conf = @exploded_conf['test_slave'] }
|
11
|
+
should "be exploded" do
|
12
|
+
assert_equal({
|
13
|
+
"adapter" => "mysql",
|
14
|
+
"encoding" => "utf8",
|
15
|
+
"database" => "ars_test",
|
16
|
+
"port" => 123,
|
17
|
+
"username" => "root",
|
18
|
+
"password" => nil,
|
19
|
+
"host" => "main_slave_host"
|
20
|
+
}, @conf)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "shard a" do
|
25
|
+
context "master" do
|
26
|
+
setup { @conf = @exploded_conf['test_shard_a'] }
|
27
|
+
should "be exploded" do
|
28
|
+
assert_equal({
|
29
|
+
"adapter" => "mysql",
|
30
|
+
"encoding" => "utf8",
|
31
|
+
"database" => "ars_test_shard_a",
|
32
|
+
"port" => 123,
|
33
|
+
"username" => "root",
|
34
|
+
"password" => nil,
|
35
|
+
"host" => "shard_a_host"
|
36
|
+
}, @conf)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "slave" do
|
41
|
+
setup { @conf = @exploded_conf['test_shard_a_slave'] }
|
42
|
+
should "be exploded" do
|
43
|
+
assert_equal({
|
44
|
+
"adapter" => "mysql",
|
45
|
+
"encoding" => "utf8",
|
46
|
+
"database" => "ars_test_shard_a",
|
47
|
+
"port" => 123,
|
48
|
+
"username" => "root",
|
49
|
+
"password" => nil,
|
50
|
+
"host" => "shard_a_slave_host"
|
51
|
+
}, @conf)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "shard b" do
|
57
|
+
context "master" do
|
58
|
+
setup { @conf = @exploded_conf['test_shard_b'] }
|
59
|
+
should "be exploded" do
|
60
|
+
assert_equal({
|
61
|
+
"adapter" => "mysql",
|
62
|
+
"encoding" => "utf8",
|
63
|
+
"database" => "ars_test_shard_b",
|
64
|
+
"port" => 123,
|
65
|
+
"username" => "root",
|
66
|
+
"password" => nil,
|
67
|
+
"host" => "shard_b_host"
|
68
|
+
}, @conf)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "slave" do
|
73
|
+
setup { @conf = @exploded_conf['test_shard_b_slave'] }
|
74
|
+
should "be exploded" do
|
75
|
+
assert_equal({
|
76
|
+
"adapter" => "mysql",
|
77
|
+
"encoding" => "utf8",
|
78
|
+
"database" => "ars_test_shard_b_slave",
|
79
|
+
"port" => 123,
|
80
|
+
"username" => "root",
|
81
|
+
"password" => nil,
|
82
|
+
"host" => "shard_b_host"
|
83
|
+
}, @conf)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require File.expand_path('helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class ConnectionSwitchenTest < ActiveSupport::TestCase
|
4
|
+
context "shard switching" do
|
5
|
+
should "only switch connection on sharded models" do
|
6
|
+
assert_using_database('ars_test', Ticket)
|
7
|
+
assert_using_database('ars_test', Account)
|
8
|
+
|
9
|
+
ActiveRecord::Base.on_shard(0) do
|
10
|
+
assert_using_database('ars_test_shard0', Ticket)
|
11
|
+
assert_using_database('ars_test', Account)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
should "switch to shard and back" do
|
16
|
+
assert_using_database('ars_test')
|
17
|
+
ActiveRecord::Base.on_slave { assert_using_database('ars_test_slave') }
|
18
|
+
|
19
|
+
ActiveRecord::Base.on_shard(0) do
|
20
|
+
assert_using_database('ars_test_shard0')
|
21
|
+
ActiveRecord::Base.on_slave { assert_using_database('ars_test_shard0_slave') }
|
22
|
+
|
23
|
+
ActiveRecord::Base.on_shard(nil) do
|
24
|
+
assert_using_database('ars_test')
|
25
|
+
ActiveRecord::Base.on_slave { assert_using_database('ars_test_slave') }
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_using_database('ars_test_shard0')
|
29
|
+
ActiveRecord::Base.on_slave { assert_using_database('ars_test_shard0_slave') }
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_using_database('ars_test')
|
33
|
+
ActiveRecord::Base.on_slave { assert_using_database('ars_test_slave') }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "slave driving" do
|
38
|
+
context "without slave configuration" do
|
39
|
+
|
40
|
+
setup do
|
41
|
+
ActiveRecord::Base.configurations.delete('test_slave')
|
42
|
+
ActiveRecord::Base.connection_handler.connection_pools.clear
|
43
|
+
ActiveRecord::Base.establish_connection('test')
|
44
|
+
end
|
45
|
+
|
46
|
+
should "default to the master database" do
|
47
|
+
Account.create!
|
48
|
+
|
49
|
+
ActiveRecord::Base.on_slave { assert_using_master_db }
|
50
|
+
Account.on_slave { assert_using_master_db }
|
51
|
+
Ticket.on_slave { assert_using_master_db }
|
52
|
+
end
|
53
|
+
|
54
|
+
should "successfully execute queries" do
|
55
|
+
Account.create!
|
56
|
+
assert_using_master_db
|
57
|
+
|
58
|
+
assert_equal Account.count, ActiveRecord::Base.on_slave { Account.count }
|
59
|
+
assert_equal Account.count, Account.on_slave { Account.count }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
context "with slave configuration" do
|
65
|
+
|
66
|
+
should "successfully execute queries" do
|
67
|
+
assert_using_master_db
|
68
|
+
Account.create!
|
69
|
+
|
70
|
+
assert_equal(1, Account.count)
|
71
|
+
assert_equal(0, ActiveRecord::Base.on_slave { Account.count })
|
72
|
+
end
|
73
|
+
|
74
|
+
should "support global on_slave blocks" do
|
75
|
+
assert_using_master_db
|
76
|
+
assert_using_master_db
|
77
|
+
|
78
|
+
ActiveRecord::Base.on_slave do
|
79
|
+
assert_using_slave_db
|
80
|
+
assert_using_slave_db
|
81
|
+
end
|
82
|
+
|
83
|
+
assert_using_master_db
|
84
|
+
assert_using_master_db
|
85
|
+
end
|
86
|
+
|
87
|
+
should "support conditional methods" do
|
88
|
+
assert_using_master_db
|
89
|
+
|
90
|
+
Account.on_slave_if(true) do
|
91
|
+
assert_using_slave_db
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_using_master_db
|
95
|
+
|
96
|
+
Account.on_slave_if(false) do
|
97
|
+
assert_using_master_db
|
98
|
+
end
|
99
|
+
|
100
|
+
Account.on_slave_unless(true) do
|
101
|
+
assert_using_master_db
|
102
|
+
end
|
103
|
+
|
104
|
+
Account.on_slave_unless(false) do
|
105
|
+
assert_using_slave_db
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
context "a model loaded with the slave" do
|
111
|
+
setup do
|
112
|
+
Account.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1000, 'master_name', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
|
113
|
+
assert(Account.find(1000))
|
114
|
+
assert_equal('master_name', Account.find(1000).name)
|
115
|
+
|
116
|
+
Account.on_slave.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1000, 'slave_name', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
|
117
|
+
|
118
|
+
@model = Account.on_slave.find(1000)
|
119
|
+
assert(@model)
|
120
|
+
assert_equal('slave_name', @model.name)
|
121
|
+
end
|
122
|
+
|
123
|
+
should "read from master on reload" do
|
124
|
+
@model.reload
|
125
|
+
assert_equal('master_name', @model.name)
|
126
|
+
end
|
127
|
+
|
128
|
+
should "be marked as read only" do
|
129
|
+
assert(@model.readonly?)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "a model loaded with the master" do
|
134
|
+
setup do
|
135
|
+
Account.connection.execute("INSERT INTO accounts (id, name, created_at, updated_at) VALUES(1000, 'master_name', '2009-12-04 20:18:48', '2009-12-04 20:18:48')")
|
136
|
+
@model = Account.first
|
137
|
+
assert(@model)
|
138
|
+
assert_equal('master_name', @model.name)
|
139
|
+
end
|
140
|
+
|
141
|
+
should "not be marked as read only" do
|
142
|
+
assert(!@model.readonly?)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "slave proxy" do
|
148
|
+
should "successfully execute queries" do
|
149
|
+
assert_using_master_db
|
150
|
+
Account.create!
|
151
|
+
|
152
|
+
assert_not_equal Account.count, Account.on_slave.count
|
153
|
+
end
|
154
|
+
|
155
|
+
should "work on association collections" do
|
156
|
+
assert_using_master_db
|
157
|
+
account = Account.create!
|
158
|
+
|
159
|
+
Ticket.connection.expects(:select_all).with("SELECT * FROM `tickets` WHERE (`tickets`.account_id = #{account.id}) LIMIT 1", anything).returns([])
|
160
|
+
Ticket.on_slave.connection.expects(:select_all).with("SELECT * FROM `tickets` WHERE (`tickets`.account_id = #{account.id}) LIMIT 1", anything).returns([])
|
161
|
+
|
162
|
+
account.tickets.first
|
163
|
+
account.tickets.on_slave.first
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|