active_record_slave 1.2.1 → 1.3.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.
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