active_record_slave 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99bdf4d7588229de3ab130af6ded5d46b2488bed
4
- data.tar.gz: 465b4f5a5c037702fe3e1a8ec0f535ceefbd3418
3
+ metadata.gz: df85e9044445ca044fcbe373312c3634f5c4d1f5
4
+ data.tar.gz: 1d31ecdaa29211fa71f852a95ab183498a26a0fb
5
5
  SHA512:
6
- metadata.gz: 63618a6ca32f59027db8a2f8b491616561c5566c25122f2e6b0e50cc44494bb0e36d6b862a388b22d8b8962f1fd1e0193864a66198af865925f17ec7a73bca0a
7
- data.tar.gz: 6ddc32f2cecd2dc61891930db2c1d532e1d4f7970b24e884faea149eece50d04b8f7646bce8e42e681220d217ee93d9653f53c342c72d5922da68ec479ea56fe
6
+ metadata.gz: fe6e8c3dfa5e7638879c954e016b6970e4432865b0a9e60e8f0d769b2201b62c571e5bda9e222770c7839f1d5884b689b0de6980b13c95290096a07572d6f94b
7
+ data.tar.gz: 38475bf316504c120a18cd7aa0e355e431dd99842370e79c1a7cb37ceaf67e48c26694e4bfc2d8c9a1339cb50a3d41313cd27c86066f16a3149969b4f02977ec
data/README.md CHANGED
@@ -1,38 +1,43 @@
1
- active_record_slave [![Build Status](https://secure.travis-ci.org/reidmorrison/active_record_slave.png?branch=master)](http://travis-ci.org/reidmorrison/active_record_slave)
2
- ===================
1
+ # active_record_slave
2
+ [![Gem Version](https://img.shields.io/gem/v/active_record_slave.svg)](https://rubygems.org/gems/active_record_slave) [![Build Status](https://travis-ci.org/rocketjob/active_record_slave.svg?branch=master)](https://travis-ci.org/rocketjob/active_record_slave) [![Downloads](https://img.shields.io/gem/dt/active_record_slave.svg)](https://rubygems.org/gems/active_record_slave) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg) [![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-Support-brightgreen.svg)](https://gitter.im/rocketjob/support)
3
3
 
4
- ActiveRecord drop-in solution to efficiently redirect reads to slave databases
4
+ Redirect ActiveRecord (Rails) reads to slave databases while ensuring all writes go to the master database.
5
5
 
6
- * http://github.com/reidmorrison/active_record_slave
6
+ * https://github.com/rocketjob/active_record_slave
7
7
 
8
8
  ## Introduction
9
9
 
10
- active_record_slave allows all database reads to go to a slave while ensuring
11
- that all writes go to the master database. Also, active_record_slave ensures that
12
- any reads that are performed in a transaction will always go to the master
10
+ Redirect database reads to slave instances while ensuring that all writes go to the master database.
11
+ Any reads that are performed within a database transaction are by default directed to the master
13
12
  database to ensure data consistency.
14
13
 
14
+ ## Status
15
+
16
+ Production Ready. Actively used in large production environments.
17
+
15
18
  ## Features
16
19
 
17
- * Redirecting reads to a single slave database
18
- * Works with any database driver that works with ActiveRecord
19
- * Supports all Rails 3 read apis, including dynamic finders, AREL, and ActiveRecord::Base.select
20
- * Transaction aware. Detects when a query is inside of a transaction and sends
21
- those reads to the master
22
- * Lightweight footprint
23
- * No overhead whatsoever when a slave is not configured
24
- * Negligible overhead when redirecting reads to the slave
25
- * Connection Pools to both databases are retained and maintained independently by ActiveRecord
20
+ * Redirect reads to a single slave database.
21
+ * Works with any database driver that works with ActiveRecord.
22
+ * Supports all Rails 3, 4, or 5 read apis.
23
+ * Including dynamic finders, AREL, and ActiveRecord::Base.select.
24
+ * Transaction aware
25
+ * Detects when a query is inside of a transaction and sends those reads to the master by default.
26
+ * Can be configured to send reads in a transaction to slave databases.
27
+ * Lightweight footprint.
28
+ * No overhead whatsoever when a slave is not configured.
29
+ * Negligible overhead when redirecting reads to the slave.
30
+ * Connection Pools to both databases are retained and maintained independently by ActiveRecord.
26
31
  * The master and slave databases do not have to be of the same type.
27
- For example one can be MySQL and the other Oracle if required.
28
- * Debug logs include a prefix of 'Slave: ' to indicate which SQL statements are going
29
- to the slave database
32
+ * For example Oracle could be the master with MySQL as the slave database.
33
+ * Debug logs include a prefix of `Slave: ` to indicate which SQL statements are going
34
+ to the slave database.
30
35
 
31
36
  ### Example showing Slave redirected read
32
37
 
33
38
  ```ruby
34
39
  # Read from the slave database
35
- r = Role.where(:name => "manager").first
40
+ r = Role.where(name: 'manager').first
36
41
  r.description = 'Manager'
37
42
 
38
43
  # Save changes back to the master database
@@ -48,7 +53,7 @@ Log file output:
48
53
 
49
54
  ```ruby
50
55
  Role.transaction do
51
- r = Role.where(:name => "manager").first
56
+ r = Role.where(name: 'manager').first
52
57
  r.description = 'Manager'
53
58
  r.save!
54
59
  end
@@ -65,7 +70,7 @@ Sometimes it is necessary to read from the master:
65
70
 
66
71
  ```ruby
67
72
  ActiveRecordSlave.read_from_master do
68
- r = Role.where(:name => "manager").first
73
+ r = Role.where(name: 'manager').first
69
74
  end
70
75
  ```
71
76
 
@@ -96,9 +101,6 @@ D, [2012-11-06T19:43:26.892697 #89002] DEBUG -- : (0.9ms) commit transaction
96
101
  By default ActiveRecordSlave detects when a call is inside a transaction and will
97
102
  send all reads to the _master_ when a transaction is active.
98
103
 
99
- With the latest Rails releases, Rails automatically wraps all Controller Action
100
- calls with a transaction, effectively sending all reads to the master database.
101
-
102
104
  It is now possible to send reads to database slaves and ignore whether currently
103
105
  inside a transaction:
104
106
 
@@ -114,22 +116,19 @@ able to read any changes already part of the transaction, but not yet committed
114
116
  and wrap those reads with `ActiveRecordSlave.read_from_master`
115
117
 
116
118
  ```ruby
117
- # Create a new inquiry
118
- Inquiry.create
119
+ Inquiry.transaction do
120
+ # Create a new inquiry
121
+ Inquiry.create
122
+
123
+ # The above inquiry is not visible yet if already in a Rails transaction.
124
+ # Use `read_from_master` to ensure it is included in the count below:
125
+ ActiveRecordSlave.read_from_master do
126
+ count = Inquiry.count
127
+ end
119
128
 
120
- # Then make sure that the new inquiry that is not yet committed is visible during
121
- # the read below:
122
- ActiveRecordSlave.read_from_master do
123
- count = Inquiry.count
124
129
  end
125
130
  ```
126
131
 
127
- ## Dependencies
128
-
129
- * Tested on Rails 3 and Rails 4
130
-
131
- See [.travis.yml](https://github.com/reidmorrison/active_record_slave/.travis.yml) for the list of tested Ruby platforms
132
-
133
132
  ## Note
134
133
 
135
134
  ActiveRecord::Base.execute is sometimes used to perform custom SQL calls against
@@ -142,7 +141,23 @@ which we do not want redirected to the slave
142
141
 
143
142
  ## Install
144
143
 
145
- gem install active_record_slave
144
+ Add to `Gemfile`
145
+
146
+ ```ruby
147
+ gem 'active_record_slave'
148
+ ```
149
+
150
+ Run bundler to install:
151
+
152
+ ```
153
+ bundle
154
+ ```
155
+
156
+ Or, without Bundler:
157
+
158
+ ```
159
+ gem install active_record_slave
160
+ ```
146
161
 
147
162
  ## Configuration
148
163
 
@@ -152,80 +167,103 @@ along with all the usual ActiveRecord database configuration options.
152
167
  For Example:
153
168
 
154
169
  ```yaml
155
- development:
156
- database: myapp_development
157
- username: root
158
- password:
170
+ production:
171
+ database: production
172
+ username: username
173
+ password: password
159
174
  encoding: utf8
160
175
  adapter: mysql
161
- host: 127.0.0.1
162
- pool: 20
176
+ host: master1
177
+ pool: 50
163
178
  slave:
164
- database: myapp_development_replica
165
- username: root
166
- password:
179
+ database: production
180
+ username: username
181
+ password: password
167
182
  encoding: utf8
168
183
  adapter: mysql
169
- host: 127.0.0.1
170
- pool: 20
184
+ host: slave1
185
+ pool: 50
171
186
  ```
172
187
 
173
188
  Sometimes it is useful to turn on slave reads per host, for example to activate
174
189
  slave reads only on the linux host 'batch':
175
190
 
176
191
  ```yaml
177
- development:
178
- database: myapp_development
179
- username: root
180
- password:
192
+ production:
193
+ database: production
194
+ username: username
195
+ password: password
181
196
  encoding: utf8
182
197
  adapter: mysql
183
- host: 127.0.0.1
184
- pool: 20
198
+ host: master1
199
+ pool: 50
185
200
  <% if `hostname`.strip == 'batch' %>
186
201
  slave:
187
- database: myapp_development_replica
188
- username: root
189
- password:
202
+ database: production
203
+ username: username
204
+ password: password
190
205
  encoding: utf8
191
206
  adapter: mysql
192
- host: 127.0.0.1
193
- pool: 20
207
+ host: slave1
208
+ pool: 50
194
209
  <% end %>
195
210
  ```
196
211
 
197
- ## Possible Future Enhancements
212
+ If there are multiple slaves, it is possible to randomly select a slave on startup
213
+ to balance the load across the slaves:
198
214
 
199
- * Support for multiple slaves (ask for it by submitting an issue)
215
+ ```yaml
216
+ production:
217
+ database: production
218
+ username: username
219
+ password: password
220
+ encoding: utf8
221
+ adapter: mysql
222
+ host: master1
223
+ pool: 50
224
+ slave:
225
+ database: production
226
+ username: username
227
+ password: password
228
+ encoding: utf8
229
+ adapter: mysql
230
+ host: <%= %w(slave1 slave2 slave3).sample %>
231
+ pool: 50
232
+ ```
200
233
 
201
- Meta
202
- ----
234
+ Slaves can also be assigned to specific hosts by using the hostname:
203
235
 
204
- * Code: `git clone git://github.com/reidmorrison/active_record_slave.git`
205
- * Home: <https://github.com/reidmorrison/active_record_slave>
206
- * Bugs: <https://github.com/reidmorrison/active_record_slave/issues>
207
- * Gems: <http://rubygems.org/gems/active_record_slave>
236
+ ```yaml
237
+ production:
238
+ database: production
239
+ username: username
240
+ password: password
241
+ encoding: utf8
242
+ adapter: mysql
243
+ host: master1
244
+ pool: 50
245
+ slave:
246
+ database: production
247
+ username: username
248
+ password: password
249
+ encoding: utf8
250
+ adapter: mysql
251
+ host: <%= `hostname`.strip == 'app1' ? 'slave1' : 'slave2' %>
252
+ pool: 50
253
+ ```
208
254
 
209
- This project uses [Semantic Versioning](http://semver.org/).
255
+ ## Dependencies
210
256
 
211
- Authors
212
- -------
257
+ See [.travis.yml](https://github.com/reidmorrison/active_record_slave/blob/master/.travis.yml) for the list of tested Ruby platforms
213
258
 
214
- Reid Morrison :: reidmo@gmail.com :: @reidmorrison
259
+ ## Possible Future Enhancements
215
260
 
216
- License
217
- -------
261
+ * Support for multiple named slaves (ask for it by submitting an issue)
218
262
 
219
- Copyright 2012, 2013, 2014 Reid Morrison
263
+ ## Versioning
220
264
 
221
- Licensed under the Apache License, Version 2.0 (the "License");
222
- you may not use this file except in compliance with the License.
223
- You may obtain a copy of the License at
265
+ This project uses [Semantic Versioning](http://semver.org/).
224
266
 
225
- http://www.apache.org/licenses/LICENSE-2.0
267
+ ## Author
226
268
 
227
- Unless required by applicable law or agreed to in writing, software
228
- distributed under the License is distributed on an "AS IS" BASIS,
229
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
230
- See the License for the specific language governing permissions and
231
- limitations under the License.
269
+ [Reid Morrison](https://github.com/reidmorrison) :: @reidmorrison
data/Rakefile CHANGED
@@ -1,8 +1,9 @@
1
- require 'rake/clean'
2
- require 'rake/testtask'
1
+ # Setup bundler to avoid having to run bundle exec all the time.
2
+ require 'rubygems'
3
+ require 'bundler/setup'
3
4
 
4
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
5
- require 'active_record_slave/version'
5
+ require 'rake/testtask'
6
+ require_relative 'lib/active_record_slave/version'
6
7
 
7
8
  task :gem do
8
9
  system "gem build active_record_slave.gemspec"
@@ -15,14 +16,16 @@ task :publish => :gem do
15
16
  system "rm active_record_slave-#{ActiveRecordSlave::VERSION}.gem"
16
17
  end
17
18
 
18
- desc "Run Test Suite"
19
- task :test do
20
- Rake::TestTask.new(:functional) do |t|
21
- t.test_files = FileList['test/*_test.rb']
22
- t.verbose = true
23
- end
24
-
25
- Rake::Task['functional'].invoke
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.pattern = 'test/**/*_test.rb'
21
+ t.verbose = true
22
+ t.warning = false
26
23
  end
27
24
 
28
- task :default => :test
25
+ # By default run tests against all appraisals
26
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
27
+ require 'appraisal'
28
+ task default: :appraisal
29
+ else
30
+ task default: :test
31
+ end
@@ -13,11 +13,12 @@ module ActiveRecordSlave
13
13
  # In a non-Rails environment, supply the environment such as
14
14
  # 'development', 'production'
15
15
  def self.install!(adapter_class = nil, environment = nil)
16
- slave_config = if ActiveRecord::Base.connection.respond_to?(:config)
17
- ActiveRecord::Base.connection.config[:slave]
18
- else
19
- ActiveRecord::Base.configurations[environment || Rails.env]['slave']
20
- end
16
+ slave_config =
17
+ if ActiveRecord::Base.connection.respond_to?(:config)
18
+ ActiveRecord::Base.connection.config[:slave]
19
+ else
20
+ ActiveRecord::Base.configurations[environment || Rails.env]['slave']
21
+ end
21
22
  if slave_config
22
23
  ActiveRecord::Base.logger.info "ActiveRecordSlave.install! v#{ActiveRecordSlave::VERSION} Establishing connection to slave database"
23
24
  Slave.establish_connection(slave_config)
@@ -25,9 +26,6 @@ module ActiveRecordSlave
25
26
  # Inject a new #select method into the ActiveRecord Database adapter
26
27
  base = adapter_class || ActiveRecord::Base.connection.class
27
28
  base.send(:include, InstanceMethods)
28
- SELECT_METHODS.each do |select_method|
29
- base.alias_method_chain(select_method, :slave_reader)
30
- end
31
29
  else
32
30
  ActiveRecord::Base.logger.info "ActiveRecordSlave not installed since no slave database defined"
33
31
  end
@@ -82,7 +80,7 @@ module ActiveRecordSlave
82
80
  # Parameters
83
81
  # variable [Symbol]
84
82
  # Name of variable to get
85
- if RUBY_VERSION.to_i >= 2
83
+ if (RUBY_VERSION.to_i >= 2) && !defined?(Rubinius::VERSION)
86
84
  # Fibers have their own thread local variables so use thread_variable_get
87
85
  def self.thread_variable_get(variable)
88
86
  Thread.current.thread_variable_get(variable)
@@ -100,7 +98,7 @@ module ActiveRecordSlave
100
98
  # Name of variable to set
101
99
  # value [Object]
102
100
  # Value to set the thread variable to
103
- if RUBY_VERSION.to_i >= 2
101
+ if (RUBY_VERSION.to_i >= 2) && !defined?(Rubinius::VERSION)
104
102
  # Fibers have their own thread local variables so use thread_variable_set
105
103
  def self.thread_variable_set(variable, value)
106
104
  Thread.current.thread_variable_set(variable, value)
@@ -11,14 +11,11 @@ module ActiveRecordSlave
11
11
  # Database Adapter method #exec_query is called for every select call
12
12
  # Replace #exec_query with one that calls the slave connection instead
13
13
  eval <<-METHOD
14
- def #{select_method}_with_slave_reader(sql, name = nil, *args)
15
- if active_record_slave_read_from_master?
16
- #{select_method}_without_slave_reader(sql, name, *args)
17
- else
18
- # Calls are going against the Slave now, prevent an infinite loop
19
- ActiveRecordSlave.read_from_master do
20
- Slave.connection.#{select_method}(sql, "Slave: \#{name || 'SQL'}", *args)
21
- end
14
+ def #{select_method}(sql, name = nil, *args)
15
+ return super if active_record_slave_read_from_master?
16
+
17
+ ActiveRecordSlave.read_from_master do
18
+ Slave.connection.#{select_method}(sql, "Slave: \#{name || 'SQL'}", *args)
22
19
  end
23
20
  end
24
21
  METHOD
@@ -17,7 +17,7 @@ module ActiveRecordSlave #:nodoc:
17
17
  config.active_record_slave = ::ActiveRecordSlave
18
18
 
19
19
  # Initialize ActiveRecordSlave
20
- initializer "load active_record_slave" , :after => "active_record.initialize_database" do
20
+ initializer "load active_record_slave", :after => "active_record.initialize_database" do
21
21
  ActiveRecordSlave.install!
22
22
  end
23
23
 
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordSlave #:nodoc
2
- VERSION = "1.2.1"
2
+ VERSION = '1.3.0'
3
3
  end
@@ -2,9 +2,9 @@ require File.join(File.dirname(__FILE__), 'test_helper')
2
2
  require 'logger'
3
3
  require 'erb'
4
4
 
5
- l = Logger.new('test.log')
6
- l.level = ::Logger::DEBUG
7
- ActiveRecord::Base.logger = l
5
+ l = Logger.new('test.log')
6
+ l.level = ::Logger::DEBUG
7
+ ActiveRecord::Base.logger = l
8
8
  ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read('test/database.yml')).result)
9
9
 
10
10
  # Define Schema in second database (slave)
@@ -42,9 +42,9 @@ ActiveRecordSlave.install!(nil, 'test')
42
42
  # Unit Test for active_record_slave
43
43
  #
44
44
  class ActiveRecordSlaveTest < Minitest::Test
45
- context 'the active_record_slave gem' do
45
+ describe 'the active_record_slave gem' do
46
46
 
47
- setup do
47
+ before do
48
48
  ActiveRecordSlave.ignore_transactions = false
49
49
 
50
50
  User.delete_all
@@ -52,16 +52,16 @@ class ActiveRecordSlaveTest < Minitest::Test
52
52
  @name = "Joe Bloggs"
53
53
  @address = "Somewhere"
54
54
  @user = User.new(
55
- :name => @name,
55
+ :name => @name,
56
56
  :address => @address
57
57
  )
58
58
  end
59
59
 
60
- teardown do
60
+ after do
61
61
  User.delete_all
62
62
  end
63
63
 
64
- should "save to master" do
64
+ it 'saves to master' do
65
65
  assert_equal true, @user.save!
66
66
  end
67
67
 
@@ -72,7 +72,7 @@ class ActiveRecordSlaveTest < Minitest::Test
72
72
  # so the tests will be verifying that reads going to the "slave" (second)
73
73
  # database do not find data written to the master.
74
74
  #
75
- should "save to master, read from slave" do
75
+ it 'saves to master, read from slave' do
76
76
  # Read from slave
77
77
  assert_equal 0, User.where(:name => @name, :address => @address).count
78
78
 
@@ -83,7 +83,7 @@ class ActiveRecordSlaveTest < Minitest::Test
83
83
  assert_equal 0, User.where(:name => @name, :address => @address).count
84
84
  end
85
85
 
86
- should "save to master, read from master when in a transaction" do
86
+ it 'save to master, read from master when in a transaction' do
87
87
  assert_equal false, ActiveRecordSlave.ignore_transactions?
88
88
 
89
89
  User.transaction do
@@ -104,7 +104,7 @@ class ActiveRecordSlaveTest < Minitest::Test
104
104
  assert_equal 0, User.where(:name => @name, :address => @address).count
105
105
  end
106
106
 
107
- should "save to master, read from slave when ignoring transactions" do
107
+ it 'save to master, read from slave when ignoring transactions' do
108
108
  ActiveRecordSlave.ignore_transactions = true
109
109
  assert_equal true, ActiveRecordSlave.ignore_transactions?
110
110
 
@@ -126,7 +126,7 @@ class ActiveRecordSlaveTest < Minitest::Test
126
126
  assert_equal 0, User.where(:name => @name, :address => @address).count
127
127
  end
128
128
 
129
- should "save to master, force a read from master even when _not_ in a transaction" do
129
+ it 'saves to master, force a read from master even when _not_ in a transaction' do
130
130
  # Read from slave
131
131
  assert_equal 0, User.where(:name => @name, :address => @address).count
132
132
 
@@ -143,4 +143,4 @@ class ActiveRecordSlaveTest < Minitest::Test
143
143
  end
144
144
 
145
145
  end
146
- end
146
+ end
data/test/test.sqlite3 CHANGED
Binary file
data/test/test_helper.rb CHANGED
@@ -1,14 +1,10 @@
1
- # Allow test to be run in-place without requiring a gem install
2
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
-
4
- # Configure Rails Environment
5
1
  ENV['RAILS_ENV'] = 'test'
6
2
 
7
3
  require 'active_record'
8
4
  require 'minitest/autorun'
9
5
  require 'minitest/reporters'
10
6
  require 'minitest/stub_any_instance'
11
- require 'shoulda/context'
12
7
  require 'active_record_slave'
8
+ require 'awesome_print'
13
9
 
14
10
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_slave
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-18 00:00:00.000000000 Z
11
+ date: 2017-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -45,7 +45,7 @@ files:
45
45
  - test/test.sqlite3
46
46
  - test/test_helper.rb
47
47
  - test/test_slave.sqlite3
48
- homepage: https://github.com/reidmorrison/active_record_slave
48
+ homepage: https://github.com/rocketjob/active_record_slave
49
49
  licenses:
50
50
  - Apache License V2.0
51
51
  metadata: {}
@@ -65,10 +65,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
65
  version: '0'
66
66
  requirements: []
67
67
  rubyforge_project:
68
- rubygems_version: 2.4.5
68
+ rubygems_version: 2.6.11
69
69
  signing_key:
70
70
  specification_version: 4
71
- summary: ActiveRecord drop-in solution to efficiently redirect reads to slave databases
71
+ summary: Redirect ActiveRecord (Rails) reads to slave databases while ensuring all
72
+ writes go to the master database.
72
73
  test_files:
73
74
  - test/active_record_slave_test.rb
74
75
  - test/database.yml