octoshark 0.0.9 → 0.1.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: e36f799c08e10ce190cd98d93fbc9133388f85cc
4
- data.tar.gz: 4d3b95a2b17155770a2f22c6d0fb7598bb192eda
3
+ metadata.gz: 055693668b801861b20b03a2b5d59cbe98a01184
4
+ data.tar.gz: 0b23a9ce02c5c88ec3c3abfae20ab9433e934221
5
5
  SHA512:
6
- metadata.gz: 62ae7717fe0960b31426328514a7bd59ec3084e409dd19cbdcf8f8b10b74348c030b36c3b3afe01c218ad5ee1790842073981f5d662f236b507ec34c7b3b5c1b
7
- data.tar.gz: 8f639d0260942633132b479193ba1c4105bf1b965929bf57a804e0a3c1ce2aaf7caa190c947ad08d749ac788a1f6fa398ec4c133481c8146b2b78d7e495fefda
6
+ metadata.gz: d2301c93076eb07afa01dead10f23c17524a5772089df8ecf5ddffca1902ff2fe3ad4022ca38db4b70a8fe6c7f8387335b6e2e2750c11cfb830a07fc14c5b9ce
7
+ data.tar.gz: 64f37f5fde130bc56398cbfc40e397f16cbd8c8a294db6638c44f0bcec799c9a7f9459ab34dbdff5d49a32f699a96e7bf13d3ae250cefd7a05dd333c2e066987
@@ -11,4 +11,5 @@ gemfile:
11
11
  - gemfiles/rails3.2.gemfile
12
12
  - gemfiles/rails4.gemfile
13
13
  - gemfiles/rails4.1.gemfile
14
+ - gemfiles/rails4.2.gemfile
14
15
  script: bundle exec rspec spec
data/Appraisals CHANGED
@@ -17,3 +17,7 @@ end
17
17
  appraise "rails4.1" do
18
18
  gem "activerecord", "~> 4.1.0"
19
19
  end
20
+
21
+ appraise "rails4.2" do
22
+ gem "activerecord", "~> 4.2.0"
23
+ end
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Travis status](https://travis-ci.org/dalibor/octoshark.png)
4
4
 
5
- Octoshark is a simple ActiveRecord connection switcher. It provides a general purpose connection switching mechanism that can be used in sharding and master-slave multi-database environments. It's up to you to specify how ActiveRecord models will use the Octoshark connections, see below for example scenarios.
5
+ Octoshark is a simple ActiveRecord connection manager. It provides a connection switching mechanism that can be used in various scenarios like: sharding, master-slave, multi-tenancy, etc. You have control over how connections are configured and used, see below for examples.
6
6
 
7
7
 
8
8
  ## Installation
@@ -28,97 +28,122 @@ $ gem install octoshark
28
28
 
29
29
  ## Usage
30
30
 
31
- Specify the connections for Octoshark to manage. This is usually done in an app initializer.
31
+ Create a new connection manager with connection 2 pools for Octoshark to manage. This usually goes in the app initializer.
32
32
 
33
33
  ```ruby
34
- Octoshark.configure({
34
+ CONN_MANAGER = Octoshark::ConnectionManager.new({
35
35
  db1: { adapter: "sqlite3", database: "db/db1.sqlite" },
36
36
  db2: { adapter: "sqlite3", database: "db/db2.sqlite" }
37
37
  })
38
38
  ```
39
39
 
40
- Configure which ActiveRecord models will use the Octoshark connection by overriding the Model.connection method. If there are many models, extract it in a module and include it.
40
+ Configure which ActiveRecord model will use the Octoshark connection by overriding the `Model.connection` method.
41
41
 
42
42
  ```ruby
43
43
  class Post < ActiveRecord::Base
44
44
  def self.connection
45
- Octoshark.current_connection
45
+ CONN_MANAGER.current_connection
46
46
  end
47
47
  end
48
48
  ```
49
49
 
50
- To use a specific database connection, do:
50
+ Alternatively, extract it as a module and include in models.
51
51
 
52
52
  ```ruby
53
- Octoshark.with_connection(:db1) do
54
- # work with db1
55
- Post.first
53
+ module ShardingModel
54
+ extend ActiveSupport::Concern
55
+
56
+ module ClassMethods
57
+ def connection
58
+ OCTOSHARK.current_connection
59
+ end
60
+ end
56
61
  end
57
62
  ```
58
63
 
59
- Octoshark connection is changed for the duration of the block and then reversed back to the previous connection.
64
+ To use a specific database connection:
60
65
 
61
- `Octoshark.current_connection` returns the active connection while in `with_connection` block, and outside it raises `Octoshark::NoCurrentConnectionError` error.
66
+ ```ruby
67
+ CONN_MANAGER.with_connection(:db1) do
68
+ # run queries on db1
69
+ Post.first
70
+ end
71
+ ```
62
72
 
63
- Multiple connection switch blocks can be nested:
73
+ Multiple `with_connection` blocks can be nested:
64
74
 
65
75
  ```ruby
66
- Octoshark.with_connection(:db1) do
67
- # work with db1
76
+ CONN_MANAGER.with_connection(:db1) do
77
+ # run queries on db1
68
78
 
69
- Octoshark.with_connection(:db2) do
70
- # work with db2
79
+ CONN_MANAGER.with_connection(:db2) do
80
+ # run queries on db2
71
81
  end
72
82
 
73
- # work with db1
83
+ # run queries on db1
74
84
  end
75
85
  ```
76
86
 
87
+ `CONN_MANAGER.current_connection` returns the active connection while in the `with_connection` block or raises `Octoshark::Error::NoCurrentConnection` otherwise.
88
+
77
89
 
78
- ## Sharding Example
90
+ ## Sharding example
79
91
 
80
- For example, let's say we have few models that are in the default Rails database (User, Account, etc) and few models that we want to shard (Blog, Post, Comment, etc). For all models in the default database, we can use the default ActiveRecord connection, and for all sharded models we need to the Octoshark connection.
92
+ Some models are in the core DB, and others in shard DBs. Shard is selected based on a user attribute. For core models use the default ActiveRecord connection and for sharded models define and use Octoshark connections.
81
93
 
82
- We specify the connection for the sharded models based on the shard key (User) in a controller with an around filter:
94
+ Switch the connection in a controller with an around filter:
83
95
 
84
96
  ```ruby
85
- # before_filter :find_user
86
97
  around_filter :select_shard
87
98
 
88
99
  def select_shard(&block)
89
- Octoshark.with_connection(current_user.shard, &block)
100
+ CONN_MANAGER.with_connection(current_user.shard, &block)
90
101
  end
91
102
  ```
92
103
 
93
- Similarly, in all other application entry-points that start with the default ActiveRecord connection (background jobs for an example), we need to switch the shard connection and then proceed.
104
+ Similar approach applies to other application entry-points like background jobs.
94
105
 
95
106
 
96
- ## Master-Slave Example
107
+ ## Master-Slave example
97
108
 
98
- When we want to do something in the slave database with all ActiveRecord models, then we need to add Octoshark's current or default connection to all models, either by overriding `ActiveRecord:Base.connection` or using a module that we include in all models.
109
+ All models are in master and slave databases. For master models use the default ActiveRecord connection and for slave models define and use Octoshark connections.
99
110
 
100
111
  ```ruby
101
112
  class ActiveRecord::Base
102
113
  def self.connection
103
- # Some rake tasks like `rake db:create` does not load initializers,
104
- # and because we're overriding ActiveRecord::Base.connection,
105
- # we need to make sure Octoshark is configured before using it.
106
- Octoshark.configure(configs) unless Octoshark.configured?
107
-
108
114
  # Return the current connection (from with_connection block) or default one
109
- Octoshark.current_or_default_connection
115
+ CONN_MANAGER.current_or_default_connection
110
116
  end
111
117
  end
112
118
  ```
113
119
 
114
- Here we use `Octoshark.current_or_default_connection` method which returns the current connection while in `with_connection` block and fallback to the default connection when outside.
120
+ `CONN_MANAGER.current_or_default_connection` method returns the current connection while in `with_connection` block or the default ActiveRecord connection when outside.
115
121
 
116
122
 
117
- ## Octoshark.reload!
123
+ ## Multi-tenant example
118
124
 
119
- Whenever ActiveRecord::Base establishes a new database connection, `Octoshark.reload!` is called. This is necessary for Octoshark to disconnect old connection pools and set new ones, otherwise `ActiveRecord::ConnectionNotEstablished` will be raised.
125
+ Some models are in the core DB, and others in user's own dedicated database. For core models use the default ActiveRecord connection and for tenant models can use Octoshark's mechanism to create new temporary connection.
120
126
 
121
- Few examples where database connections are re-established:
127
+ Switch the connection in a controller with an around filter:
128
+
129
+ ```ruby
130
+ # in initializer
131
+ CONN_MANAGER = Octoshark::ConnectionManager.new
132
+
133
+ # in controller
134
+ around_filter :select_shard
135
+
136
+ def select_shard(&block)
137
+ CONN_MANAGER.with_new_connection(name, config, reusable: false, &block)
138
+ end
139
+ ```
140
+
141
+ `CONN_MANAGER.with_new_connection` method creates a temporary connection that will automatically disconnect. If you want to reuse it in subsequent connection switches, set `reusable: true` and it will be added to the connection manager and reused with the next calls. Depends on the use-case and what's preferable. In test environment usually you would want to set it to `reusable` so that database cleaner can clean data with transaction strategy.
142
+
143
+
144
+ ## Octoshark.reset_connection_managers!
145
+
146
+ Whenever ActiveRecord::Base calls `establish_connection` (usually by an ancestor process that must have subsequently forked), `Octoshark.reset_connection_managers!` is automatically called to re-establish the Octoshark connections. It prevents `ActiveRecord::ConnectionNotEstablished` in the scenarios like:
122
147
 
123
148
  * Unicorn before/after fork
124
149
  * Spring prefork/serve
@@ -127,7 +152,7 @@ Few examples where database connections are re-established:
127
152
 
128
153
  ## Database Cleaner
129
154
 
130
- Here's an example how to clean default and shard databases using both default connection and Octoshark connections:
155
+ RSpec example on how to clean default and Octoshark data with Database Cleaner:
131
156
 
132
157
  ```ruby
133
158
  config.before(:suite) do
@@ -146,16 +171,21 @@ config.after(:each) do
146
171
  end
147
172
 
148
173
  def setup_database_cleaner
149
- Octoshark.connection_pools.each_pair do |connection_name, connection_pool|
150
- DatabaseCleaner[:active_record, {connection: connection_pool}]
174
+ DatabaseCleaner[:active_record, {connection: ActiveRecord::Base.connection_pool}]
175
+ Octoshark.connection_managers.each do |manager|
176
+ manager.connection_pools.each_pair do |connection_name, connection_pool|
177
+ DatabaseCleaner[:active_record, {connection: connection_pool}]
178
+ end
151
179
  end
152
180
  end
153
181
  ```
154
182
 
183
+
155
184
  ## Logo
156
185
 
157
186
  Thanks to [@saschamt](https://github.com/saschamt) for Octoshark logo design. :)
158
187
 
188
+
159
189
  ## Contributing
160
190
 
161
191
  1. Fork it ( http://github.com/dalibor/octoshark/fork )
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.0"
6
+
7
+ gemspec :path => "../"
@@ -3,55 +3,23 @@ require 'active_record'
3
3
  require 'octoshark/active_record_extensions'
4
4
 
5
5
  module Octoshark
6
- autoload :ConnectionSwitcher, 'octoshark/connection_switcher'
6
+ autoload :ConnectionManager, 'octoshark/connection_manager'
7
+ autoload :Error, 'octoshark/error'
7
8
 
8
- class NotConfiguredError < RuntimeError; end
9
- class NoConnectionError < StandardError; end;
10
- class NoCurrentConnectionError < StandardError; end;
9
+ # Octoshark needs to keep track of all connection managers in order to
10
+ # automatically reconnect on connection establish.
11
+ @@connection_managers = []
11
12
 
12
- OCTOSHARK = :octoshark
13
-
14
- class << self
15
- delegate :connection_pools,
16
- :current_or_default_connection,
17
- :disconnect!,
18
- :find_connection_pool,
19
- :with_connection,
20
- :without_connection,
21
- :current_connection,
22
- :current_connection?,
23
- to: :switcher
24
-
25
- def configure(configs)
26
- @configs = configs
27
- @switcher = ConnectionSwitcher.new(configs)
28
- end
29
-
30
- def reset!
31
- return unless configured?
32
- disconnect!
33
- @confings = nil
34
- @switcher = nil
35
- Thread.current[OCTOSHARK] = nil
36
- end
37
-
38
- def reload!
39
- raise_not_configured_error unless @configs
40
- disconnect!
41
- @switcher = ConnectionSwitcher.new(@configs)
42
- end
43
-
44
- def configured?
45
- !@switcher.nil?
46
- end
13
+ def self.connection_managers
14
+ @@connection_managers
15
+ end
47
16
 
48
- def switcher
49
- @switcher || raise_not_configured_error
50
- end
17
+ def self.reset_connection_managers!
18
+ connection_managers.map(&:reset!)
19
+ end
51
20
 
52
- private
53
- def raise_not_configured_error
54
- raise NotConfiguredError, "Octoshark is not configured, use Octoshark.configure"
55
- end
21
+ def self.disconnect!
22
+ connection_managers.map(&:disconnect!)
23
+ @@connection_managers = []
56
24
  end
57
25
  end
@@ -9,9 +9,13 @@ module Octoshark
9
9
  end
10
10
 
11
11
  module ClassMethods
12
+ # When a connection is established in an ancestor process that must have
13
+ # subsequently forked, ActiveRecord establishes a new connection because
14
+ # it can't reuse the existing one. When that happens, we need to reconnect
15
+ # Octoshark connection managers.
12
16
  def establish_connection_with_octoshark(*args)
13
17
  establish_connection_without_octoshark(*args)
14
- Octoshark.reload! if Octoshark.configured?
18
+ Octoshark.reset_connection_managers!
15
19
  end
16
20
  end
17
21
  end
@@ -0,0 +1,110 @@
1
+ module Octoshark
2
+ class ConnectionManager
3
+
4
+ attr_reader :connection_pools
5
+
6
+ def initialize(configs = {})
7
+ @configs = configs.with_indifferent_access
8
+ setup_connection_pools
9
+
10
+ Octoshark.connection_managers << self
11
+ end
12
+
13
+ def reset!
14
+ disconnect!
15
+ setup_connection_pools
16
+ end
17
+
18
+ def current_connection
19
+ Thread.current[identifier] || raise(Octoshark::Error::NoCurrentConnection, "No current connection")
20
+ end
21
+
22
+ def current_connection?
23
+ !Thread.current[identifier].nil?
24
+ end
25
+
26
+ def current_or_default_connection
27
+ Thread.current[identifier] || ActiveRecord::Base.connection_pool.connection
28
+ end
29
+
30
+ def with_connection(name, &block)
31
+ connection_pool = find_connection_pool(name)
32
+ with_connection_pool(name, connection_pool, &block)
33
+ end
34
+
35
+ def with_new_connection(name, config, reusable: false, &block)
36
+ if reusable
37
+ connection_pool = @connection_pools[name] ||= create_connection_pool(config)
38
+ with_connection_pool(name, connection_pool, &block)
39
+ else
40
+ connection_pool = create_connection_pool(config)
41
+ with_connection_pool(name, connection_pool, &block).tap do
42
+ connection_pool.disconnect!
43
+ end
44
+ end
45
+ end
46
+
47
+ def without_connection(&block)
48
+ change_connection_reference(nil) do
49
+ yield
50
+ end
51
+ end
52
+
53
+ def find_connection_pool(name)
54
+ @connection_pools[name] || raise(Octoshark::Error::NoConnection, "No such database connection '#{name}'")
55
+ end
56
+
57
+ def disconnect!
58
+ @connection_pools.values.each do |connection_pool|
59
+ connection_pool.disconnect!
60
+ end
61
+ end
62
+
63
+ def identifier
64
+ @identifier ||= "octoshark_#{Process.pid}"
65
+ end
66
+
67
+ private
68
+ def spec_class
69
+ if defined?(ActiveRecord::ConnectionAdapters::ConnectionSpecification)
70
+ spec_class = ActiveRecord::ConnectionAdapters::ConnectionSpecification
71
+ else
72
+ spec_class = ActiveRecord::Base::ConnectionSpecification
73
+ end
74
+ end
75
+
76
+ def change_connection_reference(connection, &block)
77
+ previous_connection = Thread.current[identifier]
78
+ Thread.current[identifier] = connection
79
+
80
+ begin
81
+ yield
82
+ ensure
83
+ Thread.current[identifier] = previous_connection
84
+ end
85
+ end
86
+
87
+ def setup_connection_pools
88
+ @connection_pools = HashWithIndifferentAccess.new
89
+
90
+ @configs.each_pair do |name, config|
91
+ @connection_pools[name] = create_connection_pool(config)
92
+ end
93
+ end
94
+
95
+ def create_connection_pool(config)
96
+ spec = spec_class.new(config, "#{config[:adapter]}_connection")
97
+ ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
98
+ end
99
+
100
+ def with_connection_pool(name, connection_pool, &block)
101
+ connection_pool.with_connection do |connection|
102
+ connection.connection_name = name
103
+
104
+ change_connection_reference(connection) do
105
+ yield(connection)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,6 @@
1
+ module Octoshark
2
+ class Error < StandardError
3
+ class NoConnection < Error; end;
4
+ class NoCurrentConnection < Error; end;
5
+ end
6
+ end
@@ -1,3 +1,3 @@
1
1
  module Octoshark
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -2,23 +2,14 @@ require 'spec_helper'
2
2
  require 'logger'
3
3
 
4
4
  describe "ActiveRecord Extensions" do
5
- it "reloads connection pools when establishing a new connection" do
6
- Octoshark.configure(configs)
7
-
8
- spec = ActiveRecord::Base.remove_connection
9
- ActiveRecord::Base.establish_connection(spec)
10
-
11
- expect(Octoshark.find_connection_pool(:default)).to eq(ActiveRecord::Base.connection_pool)
12
- end
13
-
14
5
  it "logs current connection name" do
15
6
  io = StringIO.new
16
7
  logger = Logger.new(io)
17
8
 
18
9
  ActiveRecord::Base.logger = logger
19
10
 
20
- Octoshark.configure(configs)
21
- Octoshark.with_connection(:db1) do |connection|
11
+ manager = Octoshark::ConnectionManager.new(configs)
12
+ manager.with_connection(:db1) do |connection|
22
13
  connection.execute("SELECT 1")
23
14
  end
24
15
 
@@ -33,8 +24,8 @@ describe "ActiveRecord Extensions" do
33
24
 
34
25
  ActiveRecord::Base.logger = logger
35
26
 
36
- Octoshark.configure(configs)
37
- Octoshark.with_connection(:db1) do |connection|
27
+ manager = Octoshark::ConnectionManager.new(configs)
28
+ manager.with_connection(:db1) do |connection|
38
29
  ActiveRecord::Base.connection.execute("SELECT 1")
39
30
  end
40
31
 
@@ -0,0 +1,211 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octoshark::ConnectionManager do
4
+ describe "#initialize" do
5
+ it "initializes connection manager with default connection" do
6
+ manager = Octoshark::ConnectionManager.new
7
+
8
+ expect(manager.connection_pools.length).to eq(0)
9
+ expect(manager.connection_pools[:default]).to be_nil
10
+ end
11
+
12
+ it "initializes connection manager with custom connections" do
13
+ manager = Octoshark::ConnectionManager.new(configs)
14
+
15
+ expect(manager.connection_pools.length).to eq(2)
16
+ expect(manager.connection_pools[:db1]).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
17
+ expect(manager.connection_pools[:db2]).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
18
+ end
19
+
20
+ it "accepts configs with string keys" do
21
+ configs = { 'db1' => { 'adapter' => "sqlite3", 'database' => "tmp/db1.sqlite" } }
22
+ manager = Octoshark::ConnectionManager.new(configs)
23
+
24
+ expect { manager.connection_pools[:db1].connection }.not_to raise_error
25
+ end
26
+ end
27
+
28
+ describe "#current_connection" do
29
+ it "returns last used connection as current one" do
30
+ manager = Octoshark::ConnectionManager.new(configs)
31
+ manager.with_connection(:db1) do |connection|
32
+ expect(manager.current_connection).to eq(connection)
33
+ end
34
+ end
35
+
36
+ it "raises error when no current connection" do
37
+ manager = Octoshark::ConnectionManager.new
38
+
39
+ expect { manager.current_connection }.to raise_error(Octoshark::Error::NoCurrentConnection)
40
+ end
41
+ end
42
+
43
+ describe "#current_connection?" do
44
+ it "returns true if current one" do
45
+ manager = Octoshark::ConnectionManager.new(configs)
46
+ manager.with_connection(:db1) do
47
+ expect(manager.current_connection?).to be_truthy
48
+ end
49
+ end
50
+
51
+ it "returns false if no current one" do
52
+ manager = Octoshark::ConnectionManager.new
53
+
54
+ expect(manager.current_connection?).to be_falsey
55
+ end
56
+ end
57
+
58
+ describe "#current_or_default_connection" do
59
+ it "returns current connection" do
60
+ manager = Octoshark::ConnectionManager.new(configs)
61
+ manager.with_connection(:db1) do |db1|
62
+ expect(manager.current_or_default_connection).to eq(db1)
63
+ end
64
+ end
65
+
66
+ it "returns default connection when no current connection" do
67
+ manager = Octoshark::ConnectionManager.new
68
+
69
+ expect(manager.current_or_default_connection).to eq(ActiveRecord::Base.connection_pool.connection)
70
+ end
71
+ end
72
+
73
+ describe '#find_connection_pool' do
74
+ it "can find connection pool by name" do
75
+ manager = Octoshark::ConnectionManager.new(configs)
76
+ expect(manager.find_connection_pool(:db1)).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
77
+ end
78
+
79
+ it "raises Octoshark::Error::NoConnection when no pool with that name" do
80
+ manager = Octoshark::ConnectionManager.new({})
81
+ expect { manager.find_connection_pool(:invalid) }.to raise_error(Octoshark::Error::NoConnection)
82
+ end
83
+ end
84
+
85
+ describe '#with_connection' do
86
+ it "can use multiple connections" do
87
+ manager = Octoshark::ConnectionManager.new(configs)
88
+
89
+ manager.with_connection(:db1) do
90
+ expect(db(manager.current_connection)).to eq("db1")
91
+ end
92
+
93
+ manager.with_connection(:db2) do
94
+ expect(db(manager.current_connection)).to eq("db2")
95
+ end
96
+ end
97
+
98
+ it "can nest connection" do
99
+ manager = Octoshark::ConnectionManager.new(configs)
100
+
101
+ manager.with_connection(:db1) do
102
+ expect(db(manager.current_connection)).to eq("db1")
103
+
104
+ manager.with_connection(:db2) do
105
+ expect(db(manager.current_connection)).to eq("db2")
106
+ end
107
+
108
+ expect(db(manager.current_connection)).to eq("db1")
109
+ end
110
+ end
111
+
112
+ it "returns value from execution" do
113
+ manager = Octoshark::ConnectionManager.new(configs)
114
+ result = manager.with_connection(:db1) { |connection| connection.execute("SELECT 1") }
115
+ expect(result).to eq([{"1"=>1, 0=>1}])
116
+ end
117
+
118
+ it "raises Octoshark::Error::NoConnection" do
119
+ manager = Octoshark::ConnectionManager.new({})
120
+
121
+ expect { manager.with_connection(:invalid) }.to raise_error(Octoshark::Error::NoConnection)
122
+ end
123
+ end
124
+
125
+ describe "#with_new_connection" do
126
+ it "creates temporary connection" do
127
+ manager = Octoshark::ConnectionManager.new
128
+ result = manager.with_new_connection(:db1, configs[:db1]) { |connection| connection.execute("SELECT 1") }
129
+
130
+ expect(manager.connection_pools).to be_blank
131
+ end
132
+
133
+ it "returns query results with temporary connection" do
134
+ manager = Octoshark::ConnectionManager.new
135
+ result = manager.with_new_connection(:db1, configs[:db1]) { |connection| connection.execute("SELECT 1") }
136
+
137
+ expect(result).to eq([{"1"=>1, 0=>1}])
138
+ end
139
+
140
+ it "creates persistent connection" do
141
+ connection_id = nil
142
+ manager = Octoshark::ConnectionManager.new
143
+ expect(manager.connection_pools.length).to eq(0)
144
+
145
+ manager.with_new_connection(:db1, configs[:db1], reusable: true) do |connection|
146
+ connection_id = connection.object_id
147
+ end
148
+ expect(manager.connection_pools.length).to eq(1)
149
+
150
+ manager.with_new_connection(:db1, configs[:db1], reusable: true) do |connection|
151
+ expect(connection.object_id).to eq(connection_id)
152
+ end
153
+ expect(manager.connection_pools.length).to eq(1)
154
+ end
155
+
156
+ it "returns query results with persistent connection" do
157
+ manager = Octoshark::ConnectionManager.new
158
+
159
+ result = manager.with_new_connection(:db1, configs[:db1], reusable: true) { |connection| connection.execute("SELECT 1") }
160
+ expect(result).to eq([{"1"=>1, 0=>1}])
161
+
162
+ result = manager.with_new_connection(:db1, configs[:db1], reusable: true) { |connection| connection.execute("SELECT 1") }
163
+ expect(result).to eq([{"1"=>1, 0=>1}])
164
+ end
165
+ end
166
+
167
+ describe '#without_connection' do
168
+ it "can reset current connection temporarily inside nested connection block" do
169
+ manager = Octoshark::ConnectionManager.new(configs)
170
+
171
+ manager.with_connection(:db1) do
172
+ expect(db(manager.current_connection)).to eq("db1")
173
+
174
+ manager.without_connection do
175
+ expect { manager.current_connection }.to raise_error(Octoshark::Error::NoCurrentConnection)
176
+ end
177
+
178
+ expect(db(manager.current_connection)).to eq("db1")
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "#disconnect!" do
184
+ it "removes all connections from connection pools" do
185
+ manager = Octoshark::ConnectionManager.new(configs)
186
+
187
+ manager.with_connection(:db1) { |connection| connection.execute("SELECT 1") }
188
+ expect(manager.find_connection_pool(:db1)).to be_connected
189
+
190
+ manager.disconnect!
191
+
192
+ expect(manager.find_connection_pool(:db1)).to_not be_connected
193
+ end
194
+ end
195
+
196
+ describe ".reset!" do
197
+ it "gets new connection pools ready to rock" do
198
+ manager = Octoshark::ConnectionManager.new(configs)
199
+
200
+ manager.with_connection(:db1) { |connection| connection.execute("SELECT 1") }
201
+ expect(manager.connection_pools[:db1].connections.count).to eq(1)
202
+
203
+ manager.reset!
204
+
205
+ expect(manager.connection_pools[:db1].connections.count).to eq(0)
206
+
207
+ manager.with_connection(:db1) { |connection| connection.execute("SELECT 1") }
208
+ expect(manager.connection_pools[:db1].connections.count).to eq(1)
209
+ end
210
+ end
211
+ end
@@ -2,92 +2,39 @@ require 'spec_helper'
2
2
 
3
3
  describe Octoshark do
4
4
 
5
- describe ".configure" do
6
- it "creates connection switcher" do
7
- Octoshark.configure({})
5
+ describe ".reset_connection_managers!" do
6
+ it "resets connection managers" do
7
+ manager = Octoshark::ConnectionManager.new(configs)
8
+ old_pools = manager.connection_pools.map(&:object_id)
8
9
 
9
- expect(Octoshark.switcher).to_not be_nil
10
- end
11
- end
12
-
13
- describe ".reset!" do
14
- it "removes connection switcher" do
15
- Octoshark.configure({})
16
- Octoshark.reset!
17
-
18
- expect { Octoshark.switcher }.to raise_error(Octoshark::NotConfiguredError)
19
- end
20
-
21
- it "cleans octoshark thread key" do
22
- Octoshark.configure({})
23
- Octoshark.reset!
10
+ Octoshark.reset_connection_managers!
24
11
 
25
- expect(Thread.current[Octoshark::OCTOSHARK]).to be_nil
26
- end
27
-
28
- it "cleans old connections" do
29
- check_connections_clean_up { Octoshark.reset! }
12
+ new_pools = manager.connection_pools.map(&:object_id)
13
+ expect(new_pools).to_not eq(old_pools)
30
14
  end
31
15
  end
32
16
 
33
- describe ".reload!" do
34
- it "replaces connection switcher" do
35
- Octoshark.configure({})
36
- switcher = Octoshark.switcher
17
+ describe ".disconnect!" do
18
+ it "disconnects connection managers" do
19
+ manager = Octoshark::ConnectionManager.new(configs)
37
20
 
38
- Octoshark.reload!
21
+ Octoshark.disconnect!
39
22
 
40
- expect(Octoshark.switcher).to_not be_nil
41
- expect(Octoshark.switcher).to_not eq(switcher)
23
+ expect(Octoshark.connection_managers).to be_blank
42
24
  end
43
25
 
44
- it "clears old switcher connections" do
45
- check_connections_clean_up { Octoshark.reload! }
46
- end
47
- end
48
-
49
- describe ".configured?" do
50
- it "is not configured by default" do
51
- expect(Octoshark.configured?).to be_falsey
52
- end
53
-
54
- it "is configured is switcher is configured" do
55
- Octoshark.configure({})
56
-
57
- expect(Octoshark.configured?).to be_truthy
58
- end
59
- end
60
-
61
- describe ".switcher" do
62
- it "returns connection switcher" do
63
- Octoshark.configure({})
64
-
65
- expect(Octoshark.switcher).to be_an_instance_of(Octoshark::ConnectionSwitcher)
66
- end
26
+ it "cleans old connections" do
27
+ manager = Octoshark::ConnectionManager.new(configs)
67
28
 
68
- it "raises 'NotConfiguredError' error if not configured" do
69
- expect { Octoshark.switcher }.to raise_error(Octoshark::NotConfiguredError)
70
- end
71
- end
29
+ manager.with_connection(:db1) { |connection| connection.execute("SELECT 1") }
30
+ manager.with_connection(:db2) { |connection| connection.execute("SELECT 1") }
31
+ expect(manager.connection_pools[:db1].connections.count).to eq(1)
32
+ expect(manager.connection_pools[:db2].connections.count).to eq(1)
72
33
 
73
- [
74
- :connection_pools,
75
- :current_connection,
76
- :current_connection?,
77
- :current_or_default_connection,
78
- :disconnect!,
79
- :find_connection_pool,
80
- :with_connection,
81
- :without_connection,
82
- ].each do |method_name|
83
- describe ".#{method_name}" do
84
- it "delegates #{method_name} to connection switcher" do
85
- Octoshark.configure({})
86
- expect(Octoshark.switcher).to respond_to(method_name)
87
- expect(Octoshark.switcher).to receive(method_name)
34
+ Octoshark.disconnect!
88
35
 
89
- Octoshark.send(method_name)
90
- end
36
+ expect(manager.connection_pools[:db1].connections.count).to eq(0)
37
+ expect(manager.connection_pools[:db2].connections.count).to eq(0)
91
38
  end
92
39
  end
93
40
  end
@@ -17,7 +17,6 @@ RSpec.configure do |config|
17
17
 
18
18
  config.before :each do
19
19
  ActiveRecord::Base.establish_connection({adapter: 'sqlite3', database: 'tmp/default.sqlite'})
20
- Octoshark.reset!
21
20
  end
22
21
 
23
22
  config.after :suite do
@@ -13,16 +13,4 @@ module Helpers
13
13
  split('/').last.
14
14
  split('.').first
15
15
  end
16
-
17
- def check_connections_clean_up
18
- Octoshark.configure({})
19
- switcher = Octoshark.switcher
20
-
21
- Octoshark.with_connection(:default) { |connection| connection.execute("SELECT 1") }
22
- expect(switcher.connection_pools.map { |_, c| c.connections.count }.sum).to eq(1)
23
-
24
- yield
25
-
26
- expect(switcher.connection_pools.map { |_, c| c.connections.count }.sum).to eq(0)
27
- end
28
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octoshark
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dalibor Nasevic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-25 00:00:00.000000000 Z
11
+ date: 2015-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -114,14 +114,16 @@ files:
114
114
  - gemfiles/rails3.2.gemfile
115
115
  - gemfiles/rails3.gemfile
116
116
  - gemfiles/rails4.1.gemfile
117
+ - gemfiles/rails4.2.gemfile
117
118
  - gemfiles/rails4.gemfile
118
119
  - lib/octoshark.rb
119
120
  - lib/octoshark/active_record_extensions.rb
120
- - lib/octoshark/connection_switcher.rb
121
+ - lib/octoshark/connection_manager.rb
122
+ - lib/octoshark/error.rb
121
123
  - lib/octoshark/version.rb
122
124
  - octoshark.gemspec
123
125
  - spec/octoshark/active_record_extensions_spec.rb
124
- - spec/octoshark/connection_switcher_spec.rb
126
+ - spec/octoshark/connection_manager_spec.rb
125
127
  - spec/octoshark_spec.rb
126
128
  - spec/spec_helper.rb
127
129
  - spec/support/helpers.rb
@@ -151,7 +153,7 @@ specification_version: 4
151
153
  summary: Octoshark is an ActiveRecord connection switcher
152
154
  test_files:
153
155
  - spec/octoshark/active_record_extensions_spec.rb
154
- - spec/octoshark/connection_switcher_spec.rb
156
+ - spec/octoshark/connection_manager_spec.rb
155
157
  - spec/octoshark_spec.rb
156
158
  - spec/spec_helper.rb
157
159
  - spec/support/helpers.rb
@@ -1,77 +0,0 @@
1
- module Octoshark
2
- class ConnectionSwitcher
3
-
4
- attr_reader :connection_pools
5
-
6
- def initialize(configs = {})
7
- configs = configs.with_indifferent_access
8
- @default_pool = ActiveRecord::Base.connection_pool
9
- @connection_pools = { default: @default_pool }.with_indifferent_access
10
-
11
- configs.each_pair do |name, config|
12
- spec = spec_class.new(config, "#{config[:adapter]}_connection")
13
- @connection_pools[name] = ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
14
- end
15
- end
16
-
17
- def current_connection
18
- Thread.current[OCTOSHARK] || raise(NoCurrentConnectionError, "No current connection, use Octoshark.with_connection")
19
- end
20
-
21
- def current_connection?
22
- !Thread.current[OCTOSHARK].nil?
23
- end
24
-
25
- def current_or_default_connection
26
- Thread.current[OCTOSHARK] || @default_pool.connection
27
- end
28
-
29
- def with_connection(name, &block)
30
- find_connection_pool(name).with_connection do |connection|
31
- connection.connection_name = name
32
-
33
- change_connection_reference(connection) do
34
- yield(connection)
35
- end
36
- end
37
- end
38
-
39
- def without_connection(&block)
40
- connection = nil
41
-
42
- change_connection_reference(connection) do
43
- yield(connection)
44
- end
45
- end
46
-
47
- def find_connection_pool(name)
48
- @connection_pools[name] || raise(NoConnectionError, "No such database connection '#{name}'")
49
- end
50
-
51
- def disconnect!
52
- @connection_pools.values.each do |connection_pool|
53
- connection_pool.disconnect!
54
- end
55
- end
56
-
57
- private
58
- def spec_class
59
- if defined?(ActiveRecord::ConnectionAdapters::ConnectionSpecification)
60
- spec_class = ActiveRecord::ConnectionAdapters::ConnectionSpecification
61
- else
62
- spec_class = ActiveRecord::Base::ConnectionSpecification
63
- end
64
- end
65
-
66
- def change_connection_reference(connection)
67
- previous_connection = Thread.current[OCTOSHARK]
68
- Thread.current[OCTOSHARK] = connection
69
-
70
- begin
71
- yield
72
- ensure
73
- Thread.current[OCTOSHARK] = previous_connection
74
- end
75
- end
76
- end
77
- end
@@ -1,167 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Octoshark::ConnectionSwitcher do
4
- describe "#initialize" do
5
- it "initializes connection switcher with default connection" do
6
- switcher = Octoshark::ConnectionSwitcher.new
7
- conn = ActiveRecord::Base.connection
8
-
9
- expect(switcher.connection_pools.length).to eq(1)
10
- expect(switcher.connection_pools[:default]).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
11
- end
12
-
13
- it "initializes connection switcher with custom connections" do
14
- switcher = Octoshark::ConnectionSwitcher.new(configs)
15
-
16
- expect(switcher.connection_pools.length).to eq(3)
17
- expect(switcher.connection_pools[:default]).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
18
- expect(switcher.connection_pools[:db1]).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
19
- end
20
-
21
- it "accepts configs with string keys" do
22
- configs = { 'db1' => { 'adapter' => "sqlite3", 'database' => "tmp/db1.sqlite" } }
23
- switcher = Octoshark::ConnectionSwitcher.new(configs)
24
-
25
- expect { switcher.connection_pools[:db1].connection }.not_to raise_error
26
- end
27
- end
28
-
29
- describe "#current_connection" do
30
- it "returns last used connection as current one" do
31
- switcher = Octoshark::ConnectionSwitcher.new(configs)
32
- switcher.with_connection(:db1) do |connection|
33
- expect(switcher.current_connection).to eq(connection)
34
- end
35
- end
36
-
37
- it "raises error when no current connection" do
38
- switcher = Octoshark::ConnectionSwitcher.new
39
-
40
- expect { switcher.current_connection }.to raise_error(Octoshark::NoCurrentConnectionError)
41
- end
42
- end
43
-
44
- describe "#current_connection?" do
45
- it "returns true if current one" do
46
- switcher = Octoshark::ConnectionSwitcher.new(configs)
47
- switcher.with_connection(:db1) do |connection|
48
- expect(switcher.current_connection?).to be_truthy
49
- end
50
- end
51
-
52
- it "returns false if no current one" do
53
- switcher = Octoshark::ConnectionSwitcher.new
54
-
55
- expect(switcher.current_connection?).to be_falsey
56
- end
57
- end
58
-
59
- describe "#current_or_default_connection" do
60
- it "returns current connection" do
61
- switcher = Octoshark::ConnectionSwitcher.new(configs)
62
- switcher.with_connection(:db1) do |connection|
63
- expect(switcher.current_or_default_connection).to eq(connection)
64
- end
65
- end
66
-
67
- it "returns default connection when no current connection" do
68
- switcher = Octoshark::ConnectionSwitcher.new
69
- connection = switcher.find_connection_pool(:default).connection
70
-
71
- expect(switcher.current_or_default_connection).to eq(connection)
72
- end
73
- end
74
-
75
- describe '#find_connection_pool' do
76
- it "can find connection pool by name" do
77
- switcher = Octoshark::ConnectionSwitcher.new(configs)
78
- expect(switcher.find_connection_pool(:db1)).to be_an_instance_of(ActiveRecord::ConnectionAdapters::ConnectionPool)
79
- end
80
-
81
- it "raises Octoshark::NoConnectionError when no pool with that name" do
82
- switcher = Octoshark::ConnectionSwitcher.new({})
83
- expect { switcher.find_connection_pool(:invalid) }.to raise_error(Octoshark::NoConnectionError)
84
- end
85
- end
86
-
87
- describe '#with_connection' do
88
- it "can select default connection" do
89
- switcher = Octoshark::ConnectionSwitcher.new({})
90
-
91
- switcher.with_connection(:default) do |connection|
92
- expect(db(switcher.current_connection)).to eq("default")
93
- end
94
- end
95
-
96
- it "can use multiple connections" do
97
- switcher = Octoshark::ConnectionSwitcher.new(configs)
98
-
99
- switcher.with_connection(:default) do |connection|
100
- expect(db(switcher.current_connection)).to eq("default")
101
- end
102
-
103
- switcher.with_connection(:db1) do |connection|
104
- expect(db(switcher.current_connection)).to eq("db1")
105
- end
106
-
107
- switcher.with_connection(:db2) do |connection|
108
- expect(db(switcher.current_connection)).to eq("db2")
109
- end
110
- end
111
-
112
- it "can nest connection" do
113
- switcher = Octoshark::ConnectionSwitcher.new(configs)
114
-
115
- switcher.with_connection(:db1) do |connection|
116
- expect(db(switcher.current_connection)).to eq("db1")
117
-
118
- switcher.with_connection(:db2) do |connection|
119
- expect(db(switcher.current_connection)).to eq("db2")
120
- end
121
-
122
- expect(db(switcher.current_connection)).to eq("db1")
123
- end
124
- end
125
-
126
- it "returns value from execution" do
127
- switcher = Octoshark::ConnectionSwitcher.new({})
128
- result = switcher.with_connection(:default) { |connection| connection.execute("SELECT 1") }
129
- expect(result).to eq([{"1"=>1, 0=>1}])
130
- end
131
-
132
- it "raises Octoshark::NoConnectionError" do
133
- switcher = Octoshark::ConnectionSwitcher.new({})
134
-
135
- expect { switcher.with_connection(:invalid) }.to raise_error(Octoshark::NoConnectionError)
136
- end
137
- end
138
-
139
- describe '#without_connection' do
140
- it "can reset current connection temporarily inside nested connection block" do
141
- switcher = Octoshark::ConnectionSwitcher.new({})
142
-
143
- switcher.with_connection(:default) do |connection|
144
- expect(db(switcher.current_connection)).to eq("default")
145
-
146
- switcher.without_connection do |connection|
147
- expect { switcher.current_connection }.to raise_error(Octoshark::NoCurrentConnectionError)
148
- end
149
-
150
- expect(db(switcher.current_connection)).to eq("default")
151
- end
152
- end
153
- end
154
-
155
- describe "#disconnect!" do
156
- it "removes all connections from connection pools" do
157
- switcher = Octoshark::ConnectionSwitcher.new({})
158
-
159
- switcher.with_connection(:default) { |connection| connection.execute("SELECT 1") }
160
- expect(switcher.find_connection_pool(:default)).to be_connected
161
-
162
- switcher.disconnect!
163
-
164
- expect(switcher.find_connection_pool(:default)).to_not be_connected
165
- end
166
- end
167
- end