read_from_slave 0.4.0 → 0.5.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.
- data/README +82 -25
- data/README.textile +88 -19
- data/Rakefile +0 -17
- data/VERSION.yml +1 -1
- data/lib/read_from_slave.rb +116 -44
- data/test/helper.rb +32 -22
- data/test/read_from_slave_test.rb +31 -6
- data/test/setup.rb +0 -1
- metadata +4 -4
data/README
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
Read_from_slave
|
2
2
|
|
3
|
-
Read_from_slave for Rails enables database reads from
|
3
|
+
Read_from_slave for Rails enables database reads from one or more slave databases, while writes continue to go to the master
|
4
4
|
|
5
5
|
Read_from_slave will work with Rails 2.2 and above, including Rails 3 versions.
|
6
6
|
|
7
|
+
Note that this version (0.5.x) supports multiple slave databases, but this caused an incompatibility in the configuration - your database.yml file must be updated before you can use this version.
|
8
|
+
|
7
9
|
Installation
|
8
10
|
|
9
11
|
gem install read_from_slave
|
@@ -16,24 +18,82 @@ In config/environments/production.rb (for instance)
|
|
16
18
|
|
17
19
|
In config/database.yml
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
production:
|
22
|
+
adapter: mysql
|
23
|
+
database: mydatabase
|
24
|
+
username: myuser
|
25
|
+
password: mypassword
|
26
|
+
host: my.main.database.server.com
|
27
|
+
port: 3306
|
28
|
+
slaves:
|
29
|
+
primary_slave: slave_for_reads
|
30
|
+
slave_2: slave_for_reporting
|
31
|
+
|
32
|
+
slave_for_reads:
|
33
|
+
adapter: mysql
|
34
|
+
database: mydatabase
|
35
|
+
username: myuser
|
36
|
+
password: mypassword
|
37
|
+
socket: /var/lib/mysql/mysql.sock
|
38
|
+
|
39
|
+
slave_for_reporting:
|
40
|
+
adapter: mysql
|
41
|
+
database: mydatabase
|
42
|
+
username: myuser
|
43
|
+
password: mypassword
|
44
|
+
host: my.slave.database.server.com
|
33
45
|
|
34
46
|
Just use the regular YAML format to specify your slave database, it could equally well be on
|
35
47
|
another server as the local example given above.
|
36
48
|
|
49
|
+
In the example above, primary_slave and slave_2 are names that are used to generate methods you can use to specify which slave to use.
|
50
|
+
slave_for_reads and slave_for_reporting is the yaml key that is used to make the connection to your slave database.
|
51
|
+
|
52
|
+
You can have any number of slaves configured. To connect to the slaves:
|
53
|
+
|
54
|
+
ActiveRecord::Base.with_primary_slave do
|
55
|
+
User.last
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRecord::Base.with_slave_2 do
|
59
|
+
User.last
|
60
|
+
end
|
61
|
+
|
62
|
+
with_primary_slave and with_slave_2 are dynamically created based on the keys provided in the yaml file. Everything
|
63
|
+
inside the block with read from the corresponding slave database specified in the yaml file.
|
64
|
+
|
65
|
+
You must have one slave with the primary_slave key. By default, all the reads will occur on the primary_slave,
|
66
|
+
in the example above, and not require you to use the with_primary_slave method. You actually never need to use
|
67
|
+
the with_primary_slave method as that slave will alwaysbe used if not inside a with... method block. So if you only have
|
68
|
+
1 slave, you don't ever need to worry about the with... methods.
|
69
|
+
|
70
|
+
If you have multiple slaves, you will want to make sure you specify slave configurations for each environment, even if you
|
71
|
+
don't have slaves in every environment.
|
72
|
+
|
73
|
+
development:
|
74
|
+
adapter: mysql
|
75
|
+
database: mydatabase
|
76
|
+
username: myuser
|
77
|
+
password: mypassword
|
78
|
+
host: my.main.database.server.com
|
79
|
+
port: 3306
|
80
|
+
slaves:
|
81
|
+
primary_slave: development
|
82
|
+
slave_2: development
|
83
|
+
|
84
|
+
The reason is, your code will have with_primary_slave and with_slave_2 methods because that is what you need in production,
|
85
|
+
but you will get undefined method errors in development because they won't be generated unless your development configuration
|
86
|
+
specifies slaves. Just specifying the slave keys back to the development configuration should serve your needs.
|
87
|
+
|
88
|
+
You may have a need where you don't actually want all of your reads to be on the slave. You may want to be much more
|
89
|
+
selective in what you put on the slave. For instance, perhaps you want everything on the master except large reports.
|
90
|
+
You can change the default behavior of read_from_slave by setting:
|
91
|
+
|
92
|
+
ReadFromSlave.all_reads_on_slave = false
|
93
|
+
|
94
|
+
in your environment.rb file. This will make all reads on the master by default and reads will only be on the slave inside
|
95
|
+
the with... method blocks.
|
96
|
+
|
37
97
|
Phusion Passenger
|
38
98
|
|
39
99
|
Note that if you are using Passenger, you need to make sure that the slave database is reconnected
|
@@ -45,10 +105,10 @@ The safest thing to do is to have something like this in your production.rb or e
|
|
45
105
|
|
46
106
|
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
47
107
|
if forked
|
48
|
-
# We're in smart spawning mode.
|
108
|
+
# We're in smart spawning mode.
|
49
109
|
ActiveRecord::Base.establish_slave_connections
|
50
110
|
else
|
51
|
-
# We're in conservative spawning mode. We don't need to do anything.
|
111
|
+
# We're in conservative spawning mode. We don't need to do anything.
|
52
112
|
end
|
53
113
|
end
|
54
114
|
|
@@ -63,18 +123,14 @@ Clone the git repository, and you can run the read_from_slave tests or entire Ac
|
|
63
123
|
$ rake test
|
64
124
|
...snip..
|
65
125
|
Finished in 0.046365 seconds.
|
66
|
-
|
126
|
+
|
67
127
|
7 tests, 7 assertions, 0 failures, 0 errors
|
68
|
-
|
128
|
+
|
69
129
|
$ rake test_with_active_record
|
70
130
|
...snip...
|
71
131
|
Finished in 51.904306 seconds.
|
72
|
-
|
73
|
-
2057 tests, 6685 assertions, 0 failures, 0 errors
|
74
132
|
|
75
|
-
|
76
|
-
|
77
|
-
* Support a pool of multiple slaves
|
133
|
+
2057 tests, 6685 assertions, 0 failures, 0 errors
|
78
134
|
|
79
135
|
References
|
80
136
|
|
@@ -93,4 +149,5 @@ another one, proxy connection approach
|
|
93
149
|
looks like it won't work with apps that talk to multiple (master) databases
|
94
150
|
more complex than read_from_slave
|
95
151
|
|
96
|
-
(c) 2009 Stephen Sykes
|
152
|
+
(c) 2009-2011 Stephen Sykes
|
153
|
+
Thanks to Kevin Tyll for contributing the multiple slave feature
|
data/README.textile
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
h1. Read_from_slave
|
2
2
|
|
3
|
-
h4. Read_from_slave for Rails enables database reads from
|
3
|
+
h4. Read_from_slave for Rails enables database reads from one or more slave databases, while writes continue to go to the master
|
4
4
|
|
5
5
|
Read_from_slave will work with Rails 2.2 and above, including Rails 3 versions.
|
6
6
|
|
7
|
+
Note that this version (0.5.x) supports multiple slave databases, but this caused an incompatibility in the configuration - your database.yml file must be updated before you can use this version.
|
8
|
+
|
7
9
|
h2. Installation
|
8
10
|
|
9
11
|
<pre>
|
@@ -26,26 +28,96 @@ In config/database.yml
|
|
26
28
|
|
27
29
|
<pre>
|
28
30
|
<code>
|
29
|
-
production:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
31
|
+
production:
|
32
|
+
adapter: mysql
|
33
|
+
database: mydatabase
|
34
|
+
username: myuser
|
35
|
+
password: mypassword
|
36
|
+
host: my.main.database.server.com
|
37
|
+
port: 3306
|
38
|
+
slaves:
|
39
|
+
primary_slave: slave_for_reads
|
40
|
+
slave_2: slave_for_reporting
|
41
|
+
|
42
|
+
slave_for_reads:
|
43
|
+
adapter: mysql
|
44
|
+
database: mydatabase
|
45
|
+
username: myuser
|
46
|
+
password: mypassword
|
47
|
+
socket: /var/lib/mysql/mysql.sock
|
48
|
+
|
49
|
+
slave_for_reporting:
|
50
|
+
adapter: mysql
|
51
|
+
database: mydatabase
|
52
|
+
username: myuser
|
53
|
+
password: mypassword
|
54
|
+
host: my.slave.database.server.com
|
43
55
|
</code>
|
44
56
|
</pre>
|
45
57
|
|
46
58
|
Just use the regular YAML format to specify your slave database, it could equally well be on
|
47
59
|
another server as the local example given above.
|
48
60
|
|
61
|
+
In the example above, primary_slave and slave_2 are names that are used to generate methods you can use to specify which slave to use.
|
62
|
+
slave_for_reads and slave_for_reporting is the yaml key that is used to make the connection to your slave database.
|
63
|
+
|
64
|
+
You can have any number of slaves configured. To connect to the slaves:
|
65
|
+
|
66
|
+
<pre>
|
67
|
+
<code>
|
68
|
+
ActiveRecord::Base.with_primary_slave do
|
69
|
+
User.last
|
70
|
+
end
|
71
|
+
|
72
|
+
ActiveRecord::Base.with_slave_2 do
|
73
|
+
User.last
|
74
|
+
end
|
75
|
+
</pre>
|
76
|
+
</code>
|
77
|
+
|
78
|
+
with_primary_slave and with_slave_2 are dynamically created based on the keys provided in the yaml file. Everything
|
79
|
+
inside the block with read from the corresponding slave database specified in the yaml file.
|
80
|
+
|
81
|
+
You must have one slave with the primary_slave key. By default, all the reads will occur on the primary_slave,
|
82
|
+
in the example above, and not require you to use the with_primary_slave method. You actually never need to use
|
83
|
+
the with_primary_slave method as that slave will alwaysbe used if not inside a with... method block. So if you only have
|
84
|
+
1 slave, you don't ever need to worry about the with... methods.
|
85
|
+
|
86
|
+
If you have multiple slaves, you will want to make sure you specify slave configurations for each environment, even if you
|
87
|
+
don't have slaves in every environment.
|
88
|
+
|
89
|
+
<pre>
|
90
|
+
<code>
|
91
|
+
development:
|
92
|
+
adapter: mysql
|
93
|
+
database: mydatabase
|
94
|
+
username: myuser
|
95
|
+
password: mypassword
|
96
|
+
host: my.main.database.server.com
|
97
|
+
port: 3306
|
98
|
+
slaves:
|
99
|
+
primary_slave: development
|
100
|
+
slave_2: development
|
101
|
+
</pre>
|
102
|
+
</code>
|
103
|
+
|
104
|
+
The reason is, your code will have with_primary_slave and with_slave_2 methods because that is what you need in production,
|
105
|
+
but you will get undefined method errors in development because they won't be generated unless your development configuration
|
106
|
+
specifies slaves. Just specifying the slave keys back to the development configuration should serve your needs.
|
107
|
+
|
108
|
+
You may have a need where you don't actually want all of your reads to be on the slave. You may want to be much more
|
109
|
+
selective in what you put on the slave. For instance, perhaps you want everything on the master except large reports.
|
110
|
+
You can change the default behavior of read_from_slave by setting:
|
111
|
+
|
112
|
+
<pre>
|
113
|
+
<code>
|
114
|
+
ReadFromSlave.all_reads_on_slave = false
|
115
|
+
</pre>
|
116
|
+
</code>
|
117
|
+
|
118
|
+
in your environment.rb file. This will make all reads on the master by default and reads will only be on the slave inside
|
119
|
+
the with... method blocks.
|
120
|
+
|
49
121
|
h2. Phusion Passenger
|
50
122
|
|
51
123
|
Note that if you are using Passenger, you need to make sure that the slave database is reconnected
|
@@ -92,10 +164,6 @@ Finished in 51.904306 seconds.
|
|
92
164
|
</code>
|
93
165
|
</pre>
|
94
166
|
|
95
|
-
h2. Todo
|
96
|
-
|
97
|
-
* Support a pool of multiple slaves
|
98
|
-
|
99
167
|
h2. References
|
100
168
|
|
101
169
|
* "Masochism":http://github.com/technoweenie/masochism/tree/master
|
@@ -112,3 +180,4 @@ h2. References
|
|
112
180
|
|
113
181
|
|
114
182
|
(c) 2009 Stephen Sykes
|
183
|
+
Thanks to Kevin Tyll for contributing the multiple slave feature
|
data/Rakefile
CHANGED
@@ -19,20 +19,3 @@ Rake::TestTask.new(:test_read_from_slave) do |t|
|
|
19
19
|
t.test_files = ReadFromSlave::Test.test_files
|
20
20
|
t.verbose = true
|
21
21
|
end
|
22
|
-
|
23
|
-
begin
|
24
|
-
require 'jeweler'
|
25
|
-
Jeweler::Tasks.new do |s|
|
26
|
-
s.name = "read_from_slave"
|
27
|
-
s.summary = "Read_from_slave - Utilise your slave databases with rails"
|
28
|
-
s.email = "sdsykes@gmail.com"
|
29
|
-
s.homepage = "http://github.com/sdsykes/read_from_slave"
|
30
|
-
s.description = "Read_from_slave for Rails enables database reads from a slave database, while writes continue to go to the master"
|
31
|
-
s.authors = ["Stephen Sykes"]
|
32
|
-
s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
33
|
-
end
|
34
|
-
Jeweler::GemcutterTasks.new
|
35
|
-
rescue LoadError
|
36
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://
|
37
|
-
gems.github.com"
|
38
|
-
end
|
data/VERSION.yml
CHANGED
data/lib/read_from_slave.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Read_from_slave for Rails enables database reads from
|
1
|
+
# Read_from_slave for Rails enables database reads from one or more slave databases, while writes continue
|
2
2
|
# to go to the master
|
3
3
|
# To use read_from_slave you must install the gem, configure the gem in your environment file,
|
4
4
|
# and setup your database.yml file with an entry for your slave database.
|
@@ -12,7 +12,7 @@
|
|
12
12
|
#
|
13
13
|
# config.gem "read_from_slave"
|
14
14
|
#
|
15
|
-
# In config/database.yml
|
15
|
+
# In config/database.yml
|
16
16
|
#
|
17
17
|
# production:
|
18
18
|
# adapter: mysql
|
@@ -21,16 +21,25 @@
|
|
21
21
|
# password: mypassword
|
22
22
|
# host: my.main.database.server.com
|
23
23
|
# port: 3306
|
24
|
+
# slaves:
|
25
|
+
# slave_1: slave_for_reads
|
26
|
+
# slave_2: slave_for_reporting
|
24
27
|
#
|
25
|
-
#
|
28
|
+
# slave_for_reads:
|
26
29
|
# adapter: mysql
|
27
30
|
# database: mydatabase
|
28
31
|
# username: myuser
|
29
32
|
# password: mypassword
|
30
33
|
# socket: /var/lib/mysql/mysql.sock
|
31
34
|
#
|
32
|
-
#
|
33
|
-
#
|
35
|
+
# slave_for_reporting:
|
36
|
+
# adapter: mysql
|
37
|
+
# database: mydatabase
|
38
|
+
# username: myuser
|
39
|
+
# password: mypassword
|
40
|
+
# host: my.slave.database.server.com
|
41
|
+
#
|
42
|
+
# Note that if you have multiple databases you can also configure multiple slaves.
|
34
43
|
#
|
35
44
|
# === References
|
36
45
|
# * "Masochism":http://github.com/technoweenie/masochism/tree/master
|
@@ -58,7 +67,7 @@ module ReadFromSlave
|
|
58
67
|
alias_method_chain :connection, :read_from_slave
|
59
68
|
end
|
60
69
|
end
|
61
|
-
|
70
|
+
|
62
71
|
begin
|
63
72
|
calculation_base = ActiveRecord::Relation # rails 3
|
64
73
|
calculation_base.send(:include, CalculationMethod)
|
@@ -72,6 +81,26 @@ module ReadFromSlave
|
|
72
81
|
end
|
73
82
|
end
|
74
83
|
end
|
84
|
+
|
85
|
+
def default_to_master!
|
86
|
+
base = ActiveRecord::Base
|
87
|
+
base.class_eval do
|
88
|
+
class << self
|
89
|
+
alias_method_chain :connection, :slave_db_scope unless ReadFromSlave.all_reads_on_slave
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@@all_reads_on_slave = true
|
95
|
+
def all_reads_on_slave=(all_reads)
|
96
|
+
@@all_reads_on_slave = all_reads
|
97
|
+
default_to_master!
|
98
|
+
end
|
99
|
+
|
100
|
+
def all_reads_on_slave
|
101
|
+
@@all_reads_on_slave
|
102
|
+
end
|
103
|
+
|
75
104
|
end
|
76
105
|
|
77
106
|
module InstanceMethods
|
@@ -92,84 +121,127 @@ module ReadFromSlave
|
|
92
121
|
Thread.current[:read_from_slave] = false
|
93
122
|
end
|
94
123
|
|
124
|
+
ActiveRecord::Base.connection.instance_variable_get(:@config)[:slaves].each_key do |slave_name|
|
125
|
+
eval <<-EOM
|
126
|
+
def with_#{slave_name}(&block)
|
127
|
+
Thread.current[:with_#{slave_name}_count] ||= 0
|
128
|
+
Thread.current[:with_#{slave_name}_count] += 1
|
129
|
+
yield
|
130
|
+
ensure
|
131
|
+
Thread.current[:with_#{slave_name}_count] -= 1
|
132
|
+
end
|
133
|
+
EOM
|
134
|
+
end if ActiveRecord::Base.connection.instance_variable_get(:@config)[:slaves]
|
135
|
+
|
136
|
+
def connection_with_slave_db_scope
|
137
|
+
slaves.each_key do |slave_name|
|
138
|
+
if Thread.current[:"with_#{slave_name}_count"].to_i > 0
|
139
|
+
return connection_without_slave_db_scope
|
140
|
+
end
|
141
|
+
end
|
142
|
+
connection_without_read_from_slave
|
143
|
+
end
|
144
|
+
|
95
145
|
def connection_with_read_from_slave
|
96
146
|
normal_connection = connection_without_read_from_slave
|
97
147
|
if Thread.current[:read_from_slave] && normal_connection.open_transactions == 0
|
98
|
-
|
99
|
-
|
148
|
+
slaves.each do |slave_name, slave_config|
|
149
|
+
if Thread.current[:"with_#{slave_name}_count"].to_i > 0
|
150
|
+
Thread.current[:read_from_slave_uses] = slave_name # for testing use
|
151
|
+
return slave_connection(slave_config)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# If we're not in a with_slave block, default to the primary slave
|
155
|
+
Thread.current[:read_from_slave_uses] = primary_slave_name # for testing use
|
156
|
+
return slave_connection(primary_slave_config)
|
100
157
|
else
|
101
158
|
Thread.current[:read_from_slave_uses] = :master
|
102
|
-
normal_connection
|
159
|
+
return normal_connection
|
103
160
|
end
|
104
161
|
end
|
105
162
|
|
106
|
-
# Returns a connection to the slave database, or to the regular database if
|
107
|
-
# no slave is configured
|
108
|
-
#
|
109
|
-
def slave_connection
|
110
|
-
|
163
|
+
# Returns a connection to the slave database, or to the regular database if
|
164
|
+
# no slave is configured
|
165
|
+
#
|
166
|
+
def slave_connection(slave_config)
|
167
|
+
@slave_model ||= {}
|
168
|
+
(@slave_model[slave_config] || slave_model(slave_config)).connection_without_read_from_slave
|
111
169
|
end
|
112
170
|
|
113
171
|
|
114
|
-
# Returns an AR model class that has a connection to the appropriate slave db
|
115
|
-
#
|
116
|
-
def slave_model
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@@slave_models[db_name] = eval %{
|
172
|
+
# Returns an AR model class that has a connection to the appropriate slave db
|
173
|
+
#
|
174
|
+
def slave_model(slave_config)
|
175
|
+
if slave_config_for(slave_config)
|
176
|
+
unless @@slave_models[slave_config]
|
177
|
+
slave_model_name = "ReadFromSlaveFor_#{slave_config}"
|
178
|
+
@@slave_models[slave_config] = eval %{
|
122
179
|
class #{slave_model_name} < ActiveRecord::Base
|
123
180
|
self.abstract_class = true
|
124
|
-
establish_slave_connection_for('#{
|
181
|
+
establish_slave_connection_for('#{slave_config}')
|
125
182
|
end
|
126
183
|
#{slave_model_name}
|
127
184
|
}
|
128
185
|
end
|
129
|
-
@slave_model = @@slave_models[
|
186
|
+
@slave_model[slave_config] = @@slave_models[slave_config]
|
130
187
|
else
|
131
|
-
@slave_model = self
|
188
|
+
@slave_model[slave_config] = self
|
132
189
|
end
|
133
190
|
end
|
134
191
|
|
135
|
-
# Returns the name of the database in use, as given in the database.yml file
|
136
|
-
#
|
137
|
-
def master_database_name
|
138
|
-
connection_without_read_from_slave.instance_variable_get(:@config)[:database]
|
192
|
+
# # Returns the name of the database in use, as given in the database.yml file
|
193
|
+
# #
|
194
|
+
# def master_database_name
|
195
|
+
# connection_without_read_from_slave.instance_variable_get(:@config)[:database]
|
196
|
+
# end
|
197
|
+
|
198
|
+
# Returns a hash of the slave databases, as given in the database.yml file
|
199
|
+
def slaves
|
200
|
+
connection_without_read_from_slave.instance_variable_get(:@config)[:slaves] || {}
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns the first slave defined in database.yml which will be used by default for reads
|
204
|
+
def primary_slave_config
|
205
|
+
slaves[primary_slave_name]
|
139
206
|
end
|
140
207
|
|
141
|
-
# Returns the
|
142
|
-
|
143
|
-
|
144
|
-
def slave_config_for(master)
|
145
|
-
configurations["slave_for_#{master}"]
|
208
|
+
# Returns the first slave defined in database.yml which will be used by default for reads
|
209
|
+
def primary_slave_name
|
210
|
+
:primary_slave
|
146
211
|
end
|
147
212
|
|
148
|
-
#
|
149
|
-
# the database
|
150
|
-
#
|
151
|
-
def
|
152
|
-
|
213
|
+
# Returns the config for the associated slave database for this master,
|
214
|
+
# as given in the database.yml file
|
215
|
+
#
|
216
|
+
def slave_config_for(slave_config)
|
217
|
+
configurations[slave_config]
|
218
|
+
end
|
219
|
+
|
220
|
+
# Establishes a connection to the slave database that is configured for
|
221
|
+
# the database name provided
|
222
|
+
#
|
223
|
+
def establish_slave_connection_for(slave_config)
|
224
|
+
conn_spec = slave_config_for(slave_config)
|
153
225
|
establish_connection(conn_spec) if conn_spec
|
154
226
|
end
|
155
227
|
|
156
228
|
# Re-establishes connections to all the slave databases that
|
157
|
-
# have been used so far. Use this in your
|
229
|
+
# have been used so far. Use this in your
|
158
230
|
# PhusionPassenger.on_event(:starting_worker_process) block if required.
|
159
231
|
#
|
160
232
|
def establish_slave_connections
|
161
|
-
@@slave_models.each do |
|
162
|
-
model.establish_slave_connection_for(
|
233
|
+
@@slave_models.each do |slave_config, model|
|
234
|
+
model.establish_slave_connection_for(slave_config)
|
163
235
|
end
|
164
236
|
end
|
165
237
|
end
|
166
|
-
|
238
|
+
|
167
239
|
module CalculationMethod
|
168
240
|
def calculate_with_read_from_slave(*args)
|
169
241
|
Thread.current[:read_from_slave] = true
|
170
242
|
calculate_without_read_from_slave(*args)
|
171
243
|
ensure
|
172
|
-
Thread.current[:read_from_slave] = false
|
244
|
+
Thread.current[:read_from_slave] = false
|
173
245
|
end
|
174
246
|
end
|
175
247
|
end
|
data/test/helper.rb
CHANGED
@@ -12,6 +12,7 @@ module ReadFromSlave
|
|
12
12
|
load_models
|
13
13
|
load(SCHEMA_ROOT + "/schema.rb")
|
14
14
|
require 'test/unit'
|
15
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'read_from_slave')
|
15
16
|
end
|
16
17
|
|
17
18
|
def test_files
|
@@ -25,49 +26,58 @@ module ReadFromSlave
|
|
25
26
|
private
|
26
27
|
|
27
28
|
def setup_constants
|
28
|
-
set_constant('TEST_ROOT') {File.expand_path(File.dirname(__FILE__))}
|
29
|
-
set_constant('SCHEMA_ROOT') {TEST_ROOT + "/schema"}
|
29
|
+
set_constant('TEST_ROOT') { File.expand_path(File.dirname(__FILE__)) }
|
30
|
+
set_constant('SCHEMA_ROOT') { TEST_ROOT + "/schema" }
|
30
31
|
end
|
31
|
-
|
32
|
+
|
32
33
|
def make_sqlite_config
|
33
34
|
ActiveRecord::Base.configurations = {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
35
|
+
'rfs' => {
|
36
|
+
:adapter => 'sqlite3',
|
37
|
+
:database => 'test_db',
|
38
|
+
:timeout => 5000,
|
39
|
+
:slaves =>
|
40
|
+
{:primary_slave => 'primary_slave',
|
41
|
+
:slave_2 => 'slave_2'
|
42
|
+
}
|
43
|
+
},
|
44
|
+
'primary_slave' => {
|
45
|
+
:adapter => 'sqlite3',
|
46
|
+
:database => 'test_db',
|
47
|
+
:timeout => 5000
|
48
|
+
},
|
49
|
+
'slave_2' => {
|
50
|
+
:adapter => 'sqlite3',
|
51
|
+
:database => 'test_db',
|
52
|
+
:timeout => 5000
|
53
|
+
}
|
44
54
|
}
|
45
55
|
end
|
46
|
-
|
56
|
+
|
47
57
|
def load_models
|
48
|
-
test_model_files.each {|f| require File.join(File.dirname(__FILE__), "models", f)}
|
58
|
+
test_model_files.each { |f| require File.join(File.dirname(__FILE__), "models", f) }
|
49
59
|
end
|
50
60
|
|
51
61
|
def make_sqlite_connection
|
52
62
|
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['rfs'])
|
53
63
|
end
|
54
|
-
|
64
|
+
|
55
65
|
def set_constant(constant)
|
56
66
|
Object.const_set(constant, yield) unless Object.const_defined?(constant)
|
57
67
|
end
|
58
|
-
|
68
|
+
|
59
69
|
def glob(pattern)
|
60
70
|
Dir.glob(pattern)
|
61
71
|
end
|
62
72
|
end
|
63
73
|
end
|
64
|
-
|
74
|
+
|
65
75
|
class ActiveRecordTest < Test
|
66
76
|
class << self
|
67
77
|
def setup
|
68
78
|
setup_constants
|
69
79
|
end
|
70
|
-
|
80
|
+
|
71
81
|
def test_files
|
72
82
|
glob("#{AR_TEST_SUITE}/cases/**/*_test.rb").sort
|
73
83
|
end
|
@@ -79,10 +89,10 @@ module ReadFromSlave
|
|
79
89
|
private
|
80
90
|
|
81
91
|
def setup_constants
|
82
|
-
set_constant('MYSQL_DB_USER') {'rails'}
|
83
|
-
set_constant('AR_TEST_SUITE') {find_active_record_test_suite}
|
92
|
+
set_constant('MYSQL_DB_USER') { 'rails' }
|
93
|
+
set_constant('AR_TEST_SUITE') { find_active_record_test_suite }
|
84
94
|
end
|
85
|
-
|
95
|
+
|
86
96
|
def find_active_record_test_suite
|
87
97
|
ts = ($:).grep(/activerecord/).last.split('/')
|
88
98
|
ts.pop
|
@@ -4,7 +4,7 @@ ReadFromSlave::Test.setup
|
|
4
4
|
|
5
5
|
class ReadFromSlaveTest < ActiveSupport::TestCase
|
6
6
|
test "slave connection should be different from normal connection" do
|
7
|
-
assert_not_equal Course.connection_without_read_from_slave, Course.slave_connection
|
7
|
+
assert_not_equal Course.connection_without_read_from_slave, Course.slave_connection(Course.primary_slave_config)
|
8
8
|
end
|
9
9
|
|
10
10
|
test "should be able to write and read from database" do
|
@@ -21,7 +21,7 @@ class ReadFromSlaveTest < ActiveSupport::TestCase
|
|
21
21
|
test "should read from slave" do
|
22
22
|
Course.create(:name=>"Saw playing")
|
23
23
|
x = Course.find(:first)
|
24
|
-
assert_equal :
|
24
|
+
assert_equal :primary_slave, Thread.current[:read_from_slave_uses]
|
25
25
|
end
|
26
26
|
|
27
27
|
test "should reload from master" do
|
@@ -32,9 +32,9 @@ class ReadFromSlaveTest < ActiveSupport::TestCase
|
|
32
32
|
end
|
33
33
|
|
34
34
|
test "should get new slave connection when calling establish_slave_connections" do
|
35
|
-
conn = Course.slave_connection
|
35
|
+
conn = Course.slave_connection(Course.primary_slave_config)
|
36
36
|
ActiveRecord::Base.establish_slave_connections
|
37
|
-
assert_not_equal conn, Course.slave_connection
|
37
|
+
assert_not_equal conn, Course.slave_connection(Course.primary_slave_config)
|
38
38
|
end
|
39
39
|
|
40
40
|
test "should not get new master connection when calling establish_slave_connections" do
|
@@ -44,8 +44,33 @@ class ReadFromSlaveTest < ActiveSupport::TestCase
|
|
44
44
|
end
|
45
45
|
|
46
46
|
test "count should use the slave" do
|
47
|
+
count = Course.count
|
47
48
|
Course.create(:name=>"Saw playing")
|
48
|
-
assert_equal 1, Course.count
|
49
|
-
assert_equal :
|
49
|
+
assert_equal count + 1, Course.count
|
50
|
+
assert_equal :primary_slave, Thread.current[:read_from_slave_uses]
|
51
|
+
end
|
52
|
+
|
53
|
+
test "primary_slave_config should return the right configuration" do
|
54
|
+
assert_equal 'primary_slave', Course.primary_slave_config
|
55
|
+
end
|
56
|
+
|
57
|
+
test "slave is not used by default when all_reads_on_slave is false" do
|
58
|
+
ReadFromSlave.all_reads_on_slave = false
|
59
|
+
Course.create(:name=>"Saw playing")
|
60
|
+
assert_equal :master, Thread.current[:read_from_slave_uses]
|
61
|
+
Course.first
|
62
|
+
assert_equal :master, Thread.current[:read_from_slave_uses]
|
63
|
+
end
|
64
|
+
|
65
|
+
test "correct slave is used with block" do
|
66
|
+
Course.create(:name=>"Saw playing")
|
67
|
+
Course.with_primary_slave do
|
68
|
+
Course.first
|
69
|
+
assert_equal :primary_slave, Thread.current[:read_from_slave_uses]
|
70
|
+
end
|
71
|
+
Course.with_slave_2 do
|
72
|
+
Course.first
|
73
|
+
assert_equal :slave_2, Thread.current[:read_from_slave_uses]
|
74
|
+
end
|
50
75
|
end
|
51
76
|
end
|
data/test/setup.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: read_from_slave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Stephen Sykes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-06-01 00:00:00 +03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|