slavery 3.0.0 → 4.0.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
- SHA1:
3
- metadata.gz: d8d8a53892c3dc8ba6beea003eeb3e8dda2fcb7f
4
- data.tar.gz: 884ce3b0bccb928742362447f0a1afdc9f7edc5c
2
+ SHA256:
3
+ metadata.gz: f8332b57194b74343f78c15ecdebeb7e095399c83cd881d000e3f27006fbcd81
4
+ data.tar.gz: 2ce524c9a34355b9720d1d8fb428ae2715d2be8117d5c6c9327ac2c67c4c280f
5
5
  SHA512:
6
- metadata.gz: 603d8b62b416668e3cec066d49308aa78f194557d14741e551f14280b337dcbc081e52cf48848141c1ab0a0ae8cdc948cc6ba1e85ddb177c2ad61d8a1357ffc3
7
- data.tar.gz: b0fd5c1246b7ada095fa7a1ef93a88ef1cb6d134a6a4bf0e1b2f9ce9635a1b6a3dd729059729cc5ee10f442b491a7805aeacce26fe7c5b53110e73fc9d5bf243
6
+ metadata.gz: 25c186e7781354125ba4e8c141ffbc74820b11cc5da69cd83ba1e8aa80dd472575ba9646ce7cd9107f823cebd6d252bffebcb7bf95ba4520602da5383d3109f3
7
+ data.tar.gz: 02e38582bd24b63dbeada69fc22b3567539ad90c48841bdf2f1c0ed00812f97a6ae73aae58fb9ad9a00d0e974216851e59f60085743005d1ffe06362acf7c570
data/.gitignore CHANGED
@@ -18,5 +18,5 @@ test/version_tmp
18
18
  tmp
19
19
 
20
20
  test_db
21
- test_slave_one
22
- test_slave_two
21
+ test_standby_one
22
+ test_standby_two
@@ -1,13 +1,14 @@
1
1
  language: ruby
2
2
 
3
- rvm:
4
- - 2.2.2
5
- - 2.3.3
6
-
7
- gemfile:
8
- - Gemfile
9
- - gemfiles/rails3.2.gemfile
10
- - gemfiles/rails4.gemfile
11
- - gemfiles/rails4.2.gemfile
3
+ matrix:
4
+ include:
5
+ - rvm: ruby-head
6
+ gemfile: Gemfile
7
+ - rvm: 2.5
8
+ gemfile: gemfiles/rails5.2.gemfile
9
+ - rvm: 2.5
10
+ gemfile: gemfiles/rails4.2.gemfile
11
+ - rvm: 2.2
12
+ gemfile: gemfiles/rails3.2.gemfile
12
13
 
13
14
  script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec name: 'standby'
data/README.md CHANGED
@@ -1,40 +1,40 @@
1
- # Slavery - Simple, conservative slave reads for ActiveRecord
1
+ # Standby - Read from standby databases for ActiveRecord (formerly Slavery)
2
2
 
3
3
  [![Build Status](https://travis-ci.org/kenn/slavery.svg)](https://travis-ci.org/kenn/slavery)
4
4
 
5
- Slavery is a simple, easy to use gem for ActiveRecord that enables conservative slave reads, which means it doesn't automatically redirect all SELECTs to slaves.
5
+ Standby is a simple, easy to use gem for ActiveRecord that enables conservative reading from standby databases, which means it won't automatically redirect all SELECTs to standbys.
6
6
 
7
- Instead, you can do `Slavery.on_slave { User.count }` to send a particular query to a slave.
7
+ Instead, you can do `Standby.on_standby { User.count }` to send a particular query to a standby.
8
8
 
9
- Background: Probably your app started off with one single database. As it grows, you would upgrade to a master-slave replication for redundancy. At this point, all queries still go to the master and slaves are just backups. With that configuration, it's tempting to run some long-running queries on the slave. And that's exactly what Slavery does.
9
+ Background: Probably your app started off with one single database. As it grows, you would upgrade to a primary-standby (or master-slave) replication for redundancy. At this point, all queries still go to the primary and standbys are just backups. With that configuration, it's tempting to run some long-running queries on one of the standbys. And that's exactly what Standby does.
10
10
 
11
- * Conservative - Safe by default. Installing Slavery won't change your app's current behavior.
11
+ * Conservative - Safe by default. Installing Standby won't change your app's current behavior.
12
12
  * Future proof - No dirty hacks. Simply works as a proxy for `ActiveRecord::Base.connection`.
13
13
  * Simple code - Intentionally small. You can read the entire source and completely stay in control.
14
14
 
15
- Slavery works with ActiveRecord 3 or later.
15
+ Standby works with ActiveRecord 3 or later.
16
16
 
17
17
  ## Install
18
18
 
19
19
  Add this line to your application's Gemfile:
20
20
 
21
21
  ```ruby
22
- gem 'slavery'
22
+ gem 'standby'
23
23
  ```
24
24
 
25
- And create slave configs for each environment.
25
+ And create standby configs for each environment.
26
26
 
27
27
  ```yaml
28
28
  development:
29
29
  database: myapp_development
30
30
 
31
- development_slave:
31
+ development_standby:
32
32
  database: myapp_development
33
33
  ```
34
34
 
35
- By convention, config keys with `[env]_slave` are automatically used for slave reads.
35
+ By convention, config keys with `[env]_standby` are automatically used for standby reads.
36
36
 
37
- Notice that we just copied the settings of `development` to `development_slave`. For `development` and `test`, it's actually recommended as probably you don't want to have replicating multiple databases on your machine. Two connections to the same identical database should be fine for testing purpose.
37
+ Notice that we just copied the settings of `development` to `development_standby`. For `development` and `test`, it's actually recommended as probably you don't want to have replicating multiple databases on your machine. Two connections to the same identical database should be fine for testing purpose.
38
38
 
39
39
  In case you prefer DRYer definition, YAML's aliasing and key merging might help.
40
40
 
@@ -47,7 +47,7 @@ common: &common
47
47
  development:
48
48
  <<: *common
49
49
 
50
- development_slave:
50
+ development_standby:
51
51
  <<: *common
52
52
  ```
53
53
 
@@ -55,46 +55,46 @@ Optionally, you can use a database url for your connections:
55
55
 
56
56
  ```yaml
57
57
  development: postgres://root:@localhost:5432/myapp_development
58
- development_slave: postgres://root:@localhost:5432/myapp_development_slave
58
+ development_standby: postgres://root:@localhost:5432/myapp_development_standby
59
59
  ```
60
60
 
61
- At this point, Slavery does nothing. Run tests and confirm that nothing is broken.
61
+ At this point, Standby does nothing. Run tests and confirm that nothing is broken.
62
62
 
63
63
  ## Usage
64
64
 
65
- To start using Slavery, you need to add `Slavery.on_slave` in your code. Queries in the `Slavery.on_slave` block run on the slave.
65
+ To start using Standby, you need to add `Standby.on_standby` in your code. Queries in the `Standby.on_standby` block run on the standby.
66
66
 
67
67
  ```ruby
68
- Slavery.on_slave { User.count } # => runs on slave
69
- Slavery.on_slave(:two) { User.count } # => runs on another slave configured as `development_slave_two`
68
+ Standby.on_standby { User.count } # => runs on standby
69
+ Standby.on_standby(:two) { User.count } # => runs on another standby configured as `development_standby_two`
70
70
  ```
71
71
 
72
- You can nest `on_slave` and `on_master` interchangeably. The following code works as expected.
72
+ You can nest `on_standby` and `on_primary` interchangeably. The following code works as expected.
73
73
 
74
74
  ```ruby
75
- Slavery.on_slave do
75
+ Standby.on_standby do
76
76
  ...
77
- Slavery.on_master do
77
+ Standby.on_primary do
78
78
  ...
79
79
  end
80
80
  ...
81
81
  end
82
82
  ```
83
83
 
84
- Alternatively, you may call `on_slave` directly on the scope, so that the query will be read from slave when it's executed.
84
+ Alternatively, you may call `on_standby` directly on the scope, so that the query will be read from standby when it's executed.
85
85
 
86
86
  ```ruby
87
- User.on_slave.where(active: true).count
87
+ User.on_standby.where(active: true).count
88
88
  ```
89
89
 
90
- Caveat: `pluck` is not supported by the scope syntax, you still need `Slavery.on_slave` in this case.
90
+ Caveat: `pluck` is not supported by the scope syntax, you still need `Standby.on_standby` in this case.
91
91
 
92
92
  ## Read-only user
93
93
 
94
- For an extra safeguard, it is recommended to use a read-only user for slave access.
94
+ For an extra safeguard, it is recommended to use a read-only user for standby access.
95
95
 
96
96
  ```yaml
97
- development_slave:
97
+ development_standby:
98
98
  <<: *common
99
99
  username: readonly
100
100
  ```
@@ -105,62 +105,85 @@ With MySQL, `GRANT SELECT` creates a read-only user.
105
105
  GRANT SELECT ON *.* TO 'readonly'@'localhost';
106
106
  ```
107
107
 
108
- With this user, writes on slave should raise an exception.
108
+ With this user, writes on a standby should raise an exception.
109
109
 
110
110
  ```ruby
111
- Slavery.on_slave { User.create } # => ActiveRecord::StatementInvalid: Mysql2::Error: INSERT command denied...
111
+ Standby.on_standby { User.create } # => ActiveRecord::StatementInvalid: Mysql2::Error: INSERT command denied...
112
112
  ```
113
113
 
114
114
  With Postgres you can set the entire database to be readonly:
115
115
 
116
116
  ```SQL
117
- ALTER DATABASE myapp_development_slave SET default_transaction_read_only = true;
117
+ ALTER DATABASE myapp_development_standby SET default_transaction_read_only = true;
118
118
  ```
119
119
 
120
120
  It is a good idea to confirm this behavior in your test code as well.
121
121
 
122
122
  ## Disable temporarily
123
123
 
124
- You can quickly disable slave reads by dropping the following line in `config/initializers/slavery.rb`.
124
+ You can quickly disable standby reads by dropping the following line in `config/initializers/standby.rb`.
125
125
 
126
126
  ```ruby
127
- Slavery.disabled = true
127
+ Standby.disabled = true
128
128
  ```
129
129
 
130
- With this line, Slavery stops connection switching and all queries go to the master.
130
+ With this line, Standby stops connection switching and all queries go to the primary.
131
131
 
132
- This may be useful when one of the master or the slave goes down. You would rewrite `database.yml` to make all queries go to the surviving database, until you restore or rebuild the failed one.
132
+ This may be useful when one of the primary or the standby goes down. You would rewrite `database.yml` to make all queries go to the surviving database, until you restore or rebuild the failed one.
133
133
 
134
134
  ## Transactional fixtures
135
135
 
136
136
  When `use_transactional_fixtures` is set to `true`, it's NOT recommended to
137
- write to the database besides fixtures, since the slave connection is not aware
138
- of changes performed in the master connection due to [transaction isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)).
137
+ write to the database besides fixtures, since the standby connection is not aware
138
+ of changes performed in the primary connection due to [transaction isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)).
139
139
 
140
- In that case, you are suggested to disable Slavery in the test environment by
140
+ In that case, you are suggested to disable Standby in the test environment by
141
141
  putting the following in `test/test_helper.rb`
142
142
  (or `spec/spec_helper.rb` for RSpec users):
143
143
 
144
144
  ```ruby
145
- Slavery.disabled = true
145
+ Standby.disabled = true
146
146
  ```
147
147
 
148
+ ## Upgrading from version 3 to version 4
149
+
150
+ The gem name has been changed from `slavery` to `standby`.
151
+
152
+ Update your Gemfile
153
+
154
+ ```ruby
155
+ gem 'standby'
156
+ ```
157
+
158
+ Replace `Slavery` with `Standby`, `on_slave` with `on_standby`, and `on_master` with `on_primary`.
159
+
160
+ ```sh
161
+ grep -e Slavery **/*.rake **/*.rb -s -l | xargs sed -i "" "s|Slavery|Standby|g"
162
+ grep -e on_slave **/*.rake **/*.rb -s -l | xargs sed -i "" "s|on_slave|on_standby|g"
163
+ grep -e on_master **/*.rake **/*.rb -s -l | xargs sed -i "" "s|on_master|on_primary|g"
164
+ ```
165
+
166
+ ## Upgrading from version 2 to version 3
167
+
168
+ Please note that `Standby.spec_key=` method has been removed from version 3.
169
+
148
170
  ## Support for non-Rails apps
149
171
 
150
172
  If you're using ActiveRecord in a non-Rails app (e.g. Sinatra), be sure to set `RACK_ENV` environment variable in the boot sequence, then:
151
173
 
152
174
  ```ruby
153
- require 'slavery'
175
+ require 'standby'
154
176
 
155
177
  ActiveRecord::Base.configurations = {
156
- 'development' => { adapter: 'mysql2', ... },
157
- 'development_slave' => { adapter: 'mysql2', ... }
178
+ 'development' => { adapter: 'mysql2', ... },
179
+ 'development_standby' => { adapter: 'mysql2', ... }
158
180
  }
159
181
  ActiveRecord::Base.establish_connection(:development)
160
182
  ```
161
183
 
162
184
  ## Changelog
163
185
 
164
- * v3.0.0: Support for multiple slave targets ([@punchh](https://github.com/punchh))
186
+ * v4.0.0: Rename gem from Slavery to Standby
187
+ * v3.0.0: Support for multiple standby targets ([@punchh](https://github.com/punchh))
165
188
  * v2.1.0: Debug log support / Database URL support / Rails 3.2 & 4.0 compatibility (Thanks to [@citrus](https://github.com/citrus))
166
189
  * v2.0.0: Rails 5 support
data/Rakefile CHANGED
@@ -1,4 +1,12 @@
1
- require 'bundler/gem_tasks'
1
+ module Bundler
2
+ class GemHelper
3
+ def guard_clean
4
+ true
5
+ end
6
+ end
7
+ end
8
+ # require 'bundler/gem_tasks'
9
+ Bundler::GemHelper.install_tasks(name: 'slavery')
2
10
 
3
11
  # RSpec
4
12
  require 'rspec/core/rake_task'
@@ -6,4 +6,4 @@ group :development, :test do
6
6
  gem 'test-unit', '~> 3.0'
7
7
  end
8
8
 
9
- gemspec :path => '../'
9
+ gemspec name: 'standby', path: '../'
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gem 'activerecord', '~> 4.2'
4
4
 
5
- gemspec :path => '../'
5
+ gemspec name: 'standby', path: '../'
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'activerecord', '~> 5.2'
4
+
5
+ gemspec name: 'standby', path: '../'
@@ -0,0 +1,29 @@
1
+ require 'active_record'
2
+ require 'standby/version'
3
+ require 'standby/base'
4
+ require 'standby/error'
5
+ require 'standby/connection_holder'
6
+ require 'standby/transaction'
7
+ require 'standby/active_record/base'
8
+ require 'standby/active_record/connection_handling'
9
+ require 'standby/active_record/relation'
10
+ require 'standby/active_record/log_subscriber'
11
+
12
+ module Standby
13
+ class << self
14
+ attr_accessor :disabled
15
+
16
+ def standby_connections
17
+ @standby_connections ||= {}
18
+ end
19
+
20
+ def on_standby(name = :null_state, &block)
21
+ raise Standby::Error.new('invalid standby target') unless name.is_a?(Symbol)
22
+ Base.new(name).run &block
23
+ end
24
+
25
+ def on_primary(&block)
26
+ Base.new(:primary).run &block
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class << self
4
+ alias_method :connection_without_standby, :connection
5
+
6
+ def connection
7
+ case Thread.current[:_standby]
8
+ when :primary, NilClass
9
+ connection_without_standby
10
+ else
11
+ Standby.connection_holder(Thread.current[:_standby]).connection_without_standby
12
+ end
13
+ end
14
+
15
+ # Generate scope at top level e.g. User.on_standby
16
+ def on_standby(name = :null_state)
17
+ # Why where(nil)?
18
+ # http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it
19
+ context = where(nil)
20
+ context.standby_target = name || :null_state
21
+ context
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveRecord
2
+ class LogSubscriber
3
+
4
+ alias_method :debug_without_standby, :debug
5
+
6
+ def debug(msg)
7
+ db = Standby.disabled ? "" : color("[#{Thread.current[:_standby] || "primary"}]", ActiveSupport::LogSubscriber::GREEN, true)
8
+ debug_without_standby(db + msg)
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ attr_accessor :standby_target
4
+
5
+ # Supports queries like User.on_standby.to_a
6
+ alias_method :exec_queries_without_standby, :exec_queries
7
+
8
+ def exec_queries
9
+ if standby_target
10
+ Standby.on_standby(standby_target) { exec_queries_without_standby }
11
+ else
12
+ exec_queries_without_standby
13
+ end
14
+ end
15
+
16
+
17
+ # Supports queries like User.on_standby.count
18
+ alias_method :calculate_without_standby, :calculate
19
+
20
+ def calculate(*args)
21
+ if standby_target
22
+ Standby.on_standby(standby_target) { calculate_without_standby(*args) }
23
+ else
24
+ calculate_without_standby(*args)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ module Standby
2
+ class Base
3
+ def initialize(target)
4
+ @target = decide_with(target)
5
+ end
6
+
7
+ def run(&block)
8
+ run_on @target, &block
9
+ end
10
+
11
+ private
12
+
13
+ def decide_with(target)
14
+ if Standby.disabled || target == :primary
15
+ :primary
16
+ elsif inside_transaction?
17
+ raise Standby::Error.new('on_standby cannot be used inside transaction block!')
18
+ elsif target == :null_state
19
+ :standby
20
+ elsif target.present?
21
+ "standby_#{target}".to_sym
22
+ else
23
+ raise Standby::Error.new('on_standby cannot be used with a nil target!')
24
+ end
25
+ end
26
+
27
+ def inside_transaction?
28
+ open_transactions = run_on(:primary) { ActiveRecord::Base.connection.open_transactions }
29
+ open_transactions > Standby::Transaction.base_depth
30
+ end
31
+
32
+ def run_on(target)
33
+ backup = Thread.current[:_standby] # Save for recursive nested calls
34
+ Thread.current[:_standby] = target
35
+ yield
36
+ ensure
37
+ Thread.current[:_standby] = backup
38
+ end
39
+ end
40
+ end
@@ -1,4 +1,4 @@
1
- module Slavery
1
+ module Standby
2
2
  class ConnectionHolder < ActiveRecord::Base
3
3
  self.abstract_class = true
4
4
 
@@ -6,7 +6,7 @@ module Slavery
6
6
  # for delayed activation
7
7
  def activate(target)
8
8
  spec = ActiveRecord::Base.configurations["#{ActiveRecord::ConnectionHandling::RAILS_ENV.call}_#{target}"]
9
- raise Error.new("Slave target '#{target}' is invalid!") if spec.nil?
9
+ raise Error.new("Standby target '#{target}' is invalid!") if spec.nil?
10
10
  establish_connection spec
11
11
  end
12
12
  end
@@ -14,9 +14,9 @@ module Slavery
14
14
 
15
15
  class << self
16
16
  def connection_holder(target)
17
- klass_name = "Slavery#{target.to_s.camelize}ConnectionHolder"
18
- slave_connections[klass_name] ||= begin
19
- klass = Class.new(Slavery::ConnectionHolder) do
17
+ klass_name = "Standby#{target.to_s.camelize}ConnectionHolder"
18
+ standby_connections[klass_name] ||= begin
19
+ klass = Class.new(Standby::ConnectionHolder) do
20
20
  self.abstract_class = true
21
21
  end
22
22
  Object.const_set(klass_name, klass)
@@ -1,4 +1,4 @@
1
- module Slavery
1
+ module Standby
2
2
  class Error < StandardError
3
3
  end
4
4
  end
@@ -1,4 +1,4 @@
1
- module Slavery
1
+ module Standby
2
2
  class Transaction
3
3
  # The methods on ActiveSupport::TestCase which can potentially be used
4
4
  # to determine if transactional fixtures are enabled
@@ -0,0 +1,3 @@
1
+ module Standby
2
+ VERSION = '4.0.0'
3
+ end
@@ -1,11 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'slavery/version'
4
+ require 'standby/version'
5
5
 
6
6
  Gem::Specification.new do |gem|
7
+ gem.post_install_message = 'The slavery gem has been deprecated and has ' \
8
+ 'been replaced by standby. Please switch to ' \
9
+ 'standby as soon as possible.'
7
10
  gem.name = 'slavery'
8
- gem.version = Slavery::VERSION
11
+ gem.version = Standby::VERSION
9
12
  gem.authors = ['Kenn Ejima']
10
13
  gem.email = ['kenn.ejima@gmail.com']
11
14
  gem.description = %q{Simple, conservative slave reads for ActiveRecord}
@@ -13,7 +16,7 @@ Gem::Specification.new do |gem|
13
16
  gem.homepage = 'https://github.com/kenn/slavery'
14
17
 
15
18
  gem.files = `git ls-files`.split($/)
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.executables = gem.files.grep(%r{^exe/}).map{ |f| File.basename(f) }
17
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
21
  gem.require_paths = ['lib']
19
22
 
@@ -10,30 +10,30 @@ describe ActiveRecord::LogSubscriber do
10
10
 
11
11
  before do
12
12
  ActiveRecord::Base.logger = logger
13
- @backup_disabled = Slavery.disabled
13
+ @backup_disabled = Standby.disabled
14
14
  end
15
15
 
16
16
  after do
17
- Slavery.disabled = @backup_disabled
17
+ Standby.disabled = @backup_disabled
18
18
  end
19
19
 
20
- it 'it prefixes log messages with master' do
20
+ it 'it prefixes log messages with primary' do
21
21
  User.count
22
22
  log.rewind
23
- expect(log.read).to include('[master]')
23
+ expect(log.read).to include('[primary]')
24
24
  end
25
25
 
26
- it 'it prefixes log messages with the slave connection' do
27
- User.on_slave.count
26
+ it 'it prefixes log messages with the standby connection' do
27
+ User.on_standby.count
28
28
  log.rewind
29
- expect(log.read).to include('[slave]')
29
+ expect(log.read).to include('[standby]')
30
30
  end
31
31
 
32
- it 'it does nothing when slavery is disabled' do
33
- Slavery.disabled = true
32
+ it 'it does nothing when standby is disabled' do
33
+ Standby.disabled = true
34
34
  User.count
35
35
  log.rewind
36
- expect(log.read).to_not include('[master]')
36
+ expect(log.read).to_not include('[primary]')
37
37
  end
38
38
 
39
39
  end
@@ -3,32 +3,32 @@ require 'spec_helper'
3
3
  describe 'configuration' do
4
4
  before do
5
5
  # Backup connection and configs
6
- @backup_conn = Slavery.instance_variable_get :@slave_connections
6
+ @backup_conn = Standby.instance_variable_get :@standby_connections
7
7
  @backup_config = ActiveRecord::Base.configurations.dup
8
- @backup_disabled = Slavery.disabled
8
+ @backup_disabled = Standby.disabled
9
9
  @backup_conn.each_key do |klass_name|
10
10
  Object.send(:remove_const, klass_name) if Object.const_defined?(klass_name)
11
11
  end
12
- Slavery.instance_variable_set :@slave_connections, {}
12
+ Standby.instance_variable_set :@standby_connections, {}
13
13
  end
14
14
 
15
15
  after do
16
16
  # Restore connection and configs
17
- Slavery.instance_variable_set :@slave_connections, @backup_conn
17
+ Standby.instance_variable_set :@standby_connections, @backup_conn
18
18
  ActiveRecord::Base.configurations = @backup_config
19
- Slavery.disabled = @backup_disabled
19
+ Standby.disabled = @backup_disabled
20
20
  end
21
21
 
22
- it 'raises error if slave configuration not specified' do
23
- ActiveRecord::Base.configurations['test_slave'] = nil
22
+ it 'raises error if standby configuration not specified' do
23
+ ActiveRecord::Base.configurations['test_standby'] = nil
24
24
 
25
- expect { Slavery.on_slave { User.count } }.to raise_error(Slavery::Error)
25
+ expect { Standby.on_standby { User.count } }.to raise_error(Standby::Error)
26
26
  end
27
27
 
28
- it 'connects to master if slave configuration is disabled' do
29
- ActiveRecord::Base.configurations['test_slave'] = nil
30
- Slavery.disabled = true
28
+ it 'connects to primary if standby configuration is disabled' do
29
+ ActiveRecord::Base.configurations['test_standby'] = nil
30
+ Standby.disabled = true
31
31
 
32
- expect(Slavery.on_slave { User.count }).to be 2
32
+ expect(Standby.on_standby { User.count }).to be 2
33
33
  end
34
34
  end
@@ -1,66 +1,66 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Slavery do
4
- def slavery_value
5
- Thread.current[:slavery]
3
+ describe Standby do
4
+ def standby_value
5
+ Thread.current[:_standby]
6
6
  end
7
7
 
8
- def on_slave?
9
- slavery_value == :slave
8
+ def on_standby?
9
+ standby_value == :standby
10
10
  end
11
11
 
12
12
  it 'sets thread local' do
13
- Slavery.on_master { expect(slavery_value).to be :master }
14
- Slavery.on_slave { expect(slavery_value).to be :slave }
15
- Slavery.on_slave(:two) { expect(slavery_value).to be :slave_two}
13
+ Standby.on_primary { expect(standby_value).to be :primary }
14
+ Standby.on_standby { expect(standby_value).to be :standby }
15
+ Standby.on_standby(:two) { expect(standby_value).to be :standby_two}
16
16
  end
17
17
 
18
18
  it 'returns value from block' do
19
- expect(Slavery.on_master { User.count }).to be 2
20
- expect(Slavery.on_slave { User.count }).to be 1
21
- expect(Slavery.on_slave(:two) { User.count }).to be 0
19
+ expect(Standby.on_primary { User.count }).to be 2
20
+ expect(Standby.on_standby { User.count }).to be 1
21
+ expect(Standby.on_standby(:two) { User.count }).to be 0
22
22
  end
23
23
 
24
24
  it 'handles nested calls' do
25
- # Slave -> Slave
26
- Slavery.on_slave do
27
- expect(on_slave?).to be true
25
+ # Standby -> Standby
26
+ Standby.on_standby do
27
+ expect(on_standby?).to be true
28
28
 
29
- Slavery.on_slave do
30
- expect(on_slave?).to be true
29
+ Standby.on_standby do
30
+ expect(on_standby?).to be true
31
31
  end
32
32
 
33
- expect(on_slave?).to be true
33
+ expect(on_standby?).to be true
34
34
  end
35
35
 
36
- # Slave -> Master
37
- Slavery.on_slave do
38
- expect(on_slave?).to be true
36
+ # Standby -> Primary
37
+ Standby.on_standby do
38
+ expect(on_standby?).to be true
39
39
 
40
- Slavery.on_master do
41
- expect(on_slave?).to be false
40
+ Standby.on_primary do
41
+ expect(on_standby?).to be false
42
42
  end
43
43
 
44
- expect(on_slave?).to be true
44
+ expect(on_standby?).to be true
45
45
  end
46
46
  end
47
47
 
48
48
  it 'raises error in transaction' do
49
49
  User.transaction do
50
- expect { Slavery.on_slave { User.first } }.to raise_error(Slavery::Error)
50
+ expect { Standby.on_standby { User.first } }.to raise_error(Standby::Error)
51
51
  end
52
52
  end
53
53
 
54
54
  it 'disables by configuration' do
55
- backup = Slavery.disabled
55
+ backup = Standby.disabled
56
56
 
57
- Slavery.disabled = false
58
- Slavery.on_slave { expect(slavery_value).to be :slave }
57
+ Standby.disabled = false
58
+ Standby.on_standby { expect(standby_value).to be :standby }
59
59
 
60
- Slavery.disabled = true
61
- Slavery.on_slave { expect(slavery_value).to be :master }
60
+ Standby.disabled = true
61
+ Standby.on_standby { expect(standby_value).to be :primary }
62
62
 
63
- Slavery.disabled = backup
63
+ Standby.disabled = backup
64
64
  end
65
65
 
66
66
  it 'avoids stack overflow with 3rdparty gem that defines alias_method. namely newrelic...' do
@@ -79,29 +79,29 @@ describe Slavery do
79
79
  end
80
80
  end
81
81
 
82
- it 'works with nils like slave' do
83
- expect(User.on_slave(nil).count).to be User.on_slave.count
82
+ it 'works with nils like standby' do
83
+ expect(User.on_standby(nil).count).to be User.on_standby.count
84
84
  end
85
85
 
86
86
  it 'raises on blanks and strings' do
87
- expect { User.on_slave("").count }.to raise_error(Slavery::Error)
88
- expect { User.on_slave("two").count }.to raise_error(Slavery::Error)
89
- expect { User.on_slave("slave").count }.to raise_error(Slavery::Error)
87
+ expect { User.on_standby("").count }.to raise_error(Standby::Error)
88
+ expect { User.on_standby("two").count }.to raise_error(Standby::Error)
89
+ expect { User.on_standby("standby").count }.to raise_error(Standby::Error)
90
90
  end
91
91
 
92
92
  it 'raises with non existent extension' do
93
- expect { Slavery.on_slave(:non_existent) { User.first } }.to raise_error(Slavery::Error)
93
+ expect { Standby.on_standby(:non_existent) { User.first } }.to raise_error(Standby::Error)
94
94
  end
95
95
 
96
96
  it 'works with any scopes' do
97
97
  expect(User.count).to be 2
98
- expect(User.on_slave(:two).count).to be 0
99
- expect(User.on_slave.count).to be 1
98
+ expect(User.on_standby(:two).count).to be 0
99
+ expect(User.on_standby.count).to be 1
100
100
 
101
101
  # Why where(nil)?
102
102
  # http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it
103
103
  expect(User.where(nil).to_a.size).to be 2
104
- expect(User.on_slave(:two).where(nil).to_a.size).to be 0
105
- expect(User.on_slave.where(nil).to_a.size).to be 1
104
+ expect(User.on_standby(:two).where(nil).to_a.size).to be 0
105
+ expect(User.on_standby.where(nil).to_a.size).to be 1
106
106
  end
107
107
  end
@@ -3,13 +3,13 @@ require 'bundler/setup'
3
3
 
4
4
  ENV['RACK_ENV'] = 'test'
5
5
 
6
- require 'slavery'
6
+ require 'standby'
7
7
 
8
8
  ActiveRecord::Base.configurations = {
9
9
  'test' => { 'adapter' => 'sqlite3', 'database' => 'test_db' },
10
- 'test_slave' => { 'adapter' => 'sqlite3', 'database' => 'test_slave_one' },
11
- 'test_slave_two' => { 'adapter' => 'sqlite3', 'database' => 'test_slave_two'},
12
- 'test_slave_url' => 'postgres://root:@localhost:5432/test_slave'
10
+ 'test_standby' => { 'adapter' => 'sqlite3', 'database' => 'test_standby_one' },
11
+ 'test_standby_two' => { 'adapter' => 'sqlite3', 'database' => 'test_standby_two'},
12
+ 'test_standby_url' => 'postgres://root:@localhost:5432/test_standby'
13
13
  }
14
14
 
15
15
  # Prepare databases
@@ -23,23 +23,23 @@ end
23
23
 
24
24
  class Seeder
25
25
  def run
26
- # Populate on master
26
+ # Populate on primary
27
27
  connect(:test)
28
28
  create_tables
29
29
  User.create
30
30
  User.create
31
31
  User.first.items.create
32
32
 
33
- # Populate on slave, emulating replication lag
34
- connect(:test_slave)
33
+ # Populate on standby, emulating replication lag
34
+ connect(:test_standby)
35
35
  create_tables
36
36
  User.create
37
37
 
38
- # Populate on slave two
39
- connect(:test_slave_two)
38
+ # Populate on standby two
39
+ connect(:test_standby_two)
40
40
  create_tables
41
41
 
42
- # Reconnect to master
42
+ # Reconnect to primary
43
43
  connect(:test)
44
44
  end
45
45
 
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'standby/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'standby'
8
+ gem.version = Standby::VERSION
9
+ gem.authors = ['Kenn Ejima']
10
+ gem.email = ['kenn.ejima@gmail.com']
11
+ gem.description = %q{Read from stand-by databases for ActiveRecord}
12
+ gem.summary = %q{Read from stand-by databases for ActiveRecord}
13
+ gem.homepage = 'https://github.com/kenn/standby'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^exe/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_runtime_dependency 'activerecord', '>= 3.0.0'
21
+
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'sqlite3'
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slavery
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenn Ejima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-18 00:00:00.000000000 Z
11
+ date: 2018-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -55,8 +55,7 @@ dependencies:
55
55
  description: Simple, conservative slave reads for ActiveRecord
56
56
  email:
57
57
  - kenn.ejima@gmail.com
58
- executables:
59
- - console
58
+ executables: []
60
59
  extensions: []
61
60
  extra_rdoc_files: []
62
61
  files:
@@ -70,26 +69,28 @@ files:
70
69
  - bin/console
71
70
  - gemfiles/rails3.2.gemfile
72
71
  - gemfiles/rails4.2.gemfile
73
- - gemfiles/rails4.gemfile
74
- - lib/slavery.rb
75
- - lib/slavery/active_record/base.rb
76
- - lib/slavery/active_record/connection_handling.rb
77
- - lib/slavery/active_record/log_subscriber.rb
78
- - lib/slavery/active_record/relation.rb
79
- - lib/slavery/base.rb
80
- - lib/slavery/connection_holder.rb
81
- - lib/slavery/error.rb
82
- - lib/slavery/transaction.rb
83
- - lib/slavery/version.rb
72
+ - gemfiles/rails5.2.gemfile
73
+ - lib/standby.rb
74
+ - lib/standby/active_record/base.rb
75
+ - lib/standby/active_record/connection_handling.rb
76
+ - lib/standby/active_record/log_subscriber.rb
77
+ - lib/standby/active_record/relation.rb
78
+ - lib/standby/base.rb
79
+ - lib/standby/connection_holder.rb
80
+ - lib/standby/error.rb
81
+ - lib/standby/transaction.rb
82
+ - lib/standby/version.rb
84
83
  - slavery.gemspec
85
84
  - spec/active_record/log_subscriber_spec.rb
86
85
  - spec/configuration_spec.rb
87
86
  - spec/slavery_spec.rb
88
87
  - spec/spec_helper.rb
88
+ - standby.gemspec
89
89
  homepage: https://github.com/kenn/slavery
90
90
  licenses: []
91
91
  metadata: {}
92
- post_install_message:
92
+ post_install_message: The slavery gem has been deprecated and has been replaced by
93
+ standby. Please switch to standby as soon as possible.
93
94
  rdoc_options: []
94
95
  require_paths:
95
96
  - lib
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  version: '0'
106
107
  requirements: []
107
108
  rubyforge_project:
108
- rubygems_version: 2.6.13
109
+ rubygems_version: 2.7.6
109
110
  signing_key:
110
111
  specification_version: 4
111
112
  summary: Simple, conservative slave reads for ActiveRecord
@@ -1,5 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem 'activerecord', '~> 4.0.0'
4
-
5
- gemspec :path => '../'
@@ -1,29 +0,0 @@
1
- require 'active_record'
2
- require 'slavery/version'
3
- require 'slavery/base'
4
- require 'slavery/error'
5
- require 'slavery/connection_holder'
6
- require 'slavery/transaction'
7
- require 'slavery/active_record/base'
8
- require 'slavery/active_record/connection_handling'
9
- require 'slavery/active_record/relation'
10
- require 'slavery/active_record/log_subscriber'
11
-
12
- module Slavery
13
- class << self
14
- attr_accessor :disabled
15
-
16
- def slave_connections
17
- @slave_connections ||= {}
18
- end
19
-
20
- def on_slave(name = :null_state, &block)
21
- raise Slavery::Error.new('invalid slave target') unless name.is_a?(Symbol)
22
- Base.new(name).run &block
23
- end
24
-
25
- def on_master(&block)
26
- Base.new(:master).run &block
27
- end
28
- end
29
- end
@@ -1,25 +0,0 @@
1
- module ActiveRecord
2
- class Base
3
- class << self
4
- alias_method :connection_without_slavery, :connection
5
-
6
- def connection
7
- case Thread.current[:slavery]
8
- when :master, NilClass
9
- connection_without_slavery
10
- else
11
- Slavery.connection_holder(Thread.current[:slavery]).connection_without_slavery
12
- end
13
- end
14
-
15
- # Generate scope at top level e.g. User.on_slave
16
- def on_slave(name = :null_state)
17
- # Why where(nil)?
18
- # http://stackoverflow.com/questions/18198963/with-rails-4-model-scoped-is-deprecated-but-model-all-cant-replace-it
19
- context = where(nil)
20
- context.slavery_target = name || :null_state
21
- context
22
- end
23
- end
24
- end
25
- end
@@ -1,12 +0,0 @@
1
- module ActiveRecord
2
- class LogSubscriber
3
-
4
- alias_method :debug_without_slavery, :debug
5
-
6
- def debug(msg)
7
- db = Slavery.disabled ? "" : color("[#{Thread.current[:slavery] || "master"}]", ActiveSupport::LogSubscriber::GREEN, true)
8
- debug_without_slavery(db + msg)
9
- end
10
-
11
- end
12
- end
@@ -1,28 +0,0 @@
1
- module ActiveRecord
2
- class Relation
3
- attr_accessor :slavery_target
4
-
5
- # Supports queries like User.on_slave.to_a
6
- alias_method :exec_queries_without_slavery, :exec_queries
7
-
8
- def exec_queries
9
- if slavery_target
10
- Slavery.on_slave(slavery_target) { exec_queries_without_slavery }
11
- else
12
- exec_queries_without_slavery
13
- end
14
- end
15
-
16
-
17
- # Supports queries like User.on_slave.count
18
- alias_method :calculate_without_slavery, :calculate
19
-
20
- def calculate(*args)
21
- if slavery_target
22
- Slavery.on_slave(slavery_target) { calculate_without_slavery(*args) }
23
- else
24
- calculate_without_slavery(*args)
25
- end
26
- end
27
- end
28
- end
@@ -1,40 +0,0 @@
1
- module Slavery
2
- class Base
3
- def initialize(target)
4
- @target = decide_with(target)
5
- end
6
-
7
- def run(&block)
8
- run_on @target, &block
9
- end
10
-
11
- private
12
-
13
- def decide_with(target)
14
- if Slavery.disabled || target == :master
15
- :master
16
- elsif inside_transaction?
17
- raise Slavery::Error.new('on_slave cannot be used inside transaction block!')
18
- elsif target == :null_state
19
- :slave
20
- elsif target.present?
21
- "slave_#{target}".to_sym
22
- else
23
- raise Slavery::Error.new('on_slave cannot be used with a nil target!')
24
- end
25
- end
26
-
27
- def inside_transaction?
28
- open_transactions = run_on(:master) { ActiveRecord::Base.connection.open_transactions }
29
- open_transactions > Slavery::Transaction.base_depth
30
- end
31
-
32
- def run_on(target)
33
- backup = Thread.current[:slavery] # Save for recursive nested calls
34
- Thread.current[:slavery] = target
35
- yield
36
- ensure
37
- Thread.current[:slavery] = backup
38
- end
39
- end
40
- end
@@ -1,3 +0,0 @@
1
- module Slavery
2
- VERSION = '3.0.0'
3
- end