connection_manager 1.0.4 → 1.1.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 +8 -8
- data/CHANGE.md +12 -1
- data/README.md +99 -82
- data/connection_manager.gemspec +7 -11
- data/lib/connection_manager/builder.rb +56 -0
- data/lib/connection_manager/connection_adapters/abstract_adapter.rb +50 -0
- data/lib/connection_manager/connection_adapters/mysql_adapter.rb +39 -0
- data/lib/connection_manager/connection_handling.rb +91 -0
- data/lib/connection_manager/core.rb +24 -0
- data/lib/connection_manager/querying.rb +13 -0
- data/lib/connection_manager/railtie.rb +10 -0
- data/lib/connection_manager/relation.rb +21 -0
- data/lib/connection_manager/replication.rb +56 -85
- data/lib/connection_manager/shards.rb +2 -1
- data/lib/connection_manager/using.rb +30 -24
- data/lib/connection_manager/version.rb +1 -1
- data/lib/connection_manager.rb +26 -16
- data/spec/{mysql2_database.yml → database.yml} +30 -26
- data/spec/factories.rb +1 -1
- data/spec/helpers/database_spec_helper.rb +91 -62
- data/spec/helpers/models_spec_helper.rb +23 -12
- data/spec/lib/builder_spec.rb +31 -0
- data/spec/lib/connection_adapters/abstract_adapter_spec.rb +48 -0
- data/spec/lib/connection_adapters/mysql_adapter_spec.rb +13 -0
- data/spec/lib/connection_handling_spec.rb +65 -0
- data/spec/lib/core_spec.rb +10 -0
- data/spec/lib/integration/cross_schema_spec.rb +35 -0
- data/spec/lib/querying_spec.rb +19 -0
- data/spec/lib/relation_spec.rb +21 -0
- data/spec/lib/replication_spec.rb +19 -57
- data/spec/lib/shards_spec.rb +3 -3
- data/spec/lib/using_proxy_spec.rb +27 -0
- data/spec/lib/using_spec.rb +28 -15
- data/spec/spec_helper.rb +2 -10
- metadata +73 -35
- data/lib/connection_manager/connection_builder.rb +0 -82
- data/lib/connection_manager/connection_manager_railtie.rb +0 -8
- data/lib/connection_manager/helpers/abstract_adapter_helper.rb +0 -95
- data/lib/connection_manager/helpers/connection_helpers.rb +0 -119
- data/lib/connection_manager/patches/cross_schema_patch.rb +0 -67
- data/spec/jdbcmysql_database.yml +0 -50
- data/spec/lib/connection_builder_spec.rb +0 -28
- data/spec/lib/connection_helpers_spec.rb +0 -79
- data/spec/lib/patches/cross_schema_path_spec.rb +0 -74
- data/spec/sqlite_database.yml +0 -26
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MGZjYTQyMmJjZDkyODU2MDMyYWU4OGJmYjRkZTg4ZmNjMmM3YmYwNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MjVhMjBlN2NhZGU3ZmVlYmJiOGRjODUxZmYwMDFjOTE3N2I3OGI0ZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjFmMDZkNDE3NjgxMWU2NTRiNDZlMGZhMmJkN2JmZmE4YjQwMDdhY2UxMDY3
|
10
|
+
NDEzYmM0OTk2MmZlNGRiNTYyZWM4ZWE4OTc1MTc3NjRmY2ZhMzI1OTk5YWQ3
|
11
|
+
MWNkYjY2MGNmNDdjMTdhMjZhODE1ZWY4ZTE5NTRmZDRlNDMzMGE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MzYwMjJjMzU5NWRlODk3MGRjMmEzMWI2ODJkNThkMGRiMjUzNWE1NmMxMjQ5
|
14
|
+
YzkzMjQzNjAyMmEzZDg5MTVmMjI0ZWMwMDdhMjcyY2E4ZjVmZTY2OWFkZTVi
|
15
|
+
MGI5MWUwZTkxYTAyZTZlYWYwZjBlMmZmYzE2MDkwMzE0MTYxMjE=
|
data/CHANGE.md
CHANGED
@@ -3,7 +3,18 @@ ConnectionManager Changelog
|
|
3
3
|
|
4
4
|
HEAD
|
5
5
|
=======
|
6
|
-
-
|
6
|
+
- Nothing yet!
|
7
|
+
|
8
|
+
1.1.0
|
9
|
+
=======
|
10
|
+
- BREAKING CHANGE - Code has been organized to mirror as much a possible their ActiveRecord 4 counter parts.
|
11
|
+
- BREAKING CHANGE - creates AR::Relation for :slaves and :masters, replication method names are no longer customizable
|
12
|
+
- BREAKING CHANGE - Drop support for forced readonly, should be enforced by DMS and or using #readonly ActiveRecord::Relation
|
13
|
+
- BREAKING CHANGE - Drop use_schema in favor of schema_name=
|
14
|
+
- BREAKING CHANGE - Drop current_database_name in favor of schema_name
|
15
|
+
- Make sure all connections are checked in to managed_connections by patch establish_connection
|
16
|
+
- Use thread-safe for managed_connections
|
17
|
+
- Don't try and fetch schema using query, too slow and buggy, we want a light weight implementation, but still get it from database.yml if Mysql and not set
|
7
18
|
|
8
19
|
1.0.4
|
9
20
|
=======
|
data/README.md
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
# ConnectionManager
|
2
|
-
|
3
|
-
|
4
|
-
##
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
patching, but a gem helps to reduce redundant code and ensure consistency.
|
9
|
-
ConnectionManager replaces all the connection classes and subclasses required
|
10
|
-
for multiple database support in Rails with a few class methods and simple
|
11
|
-
database.yml configuration. Since ConnectionManager does not alter
|
12
|
-
ActiveRecord's connection pool, thread safety is not a concern.
|
2
|
+
Improved cross-schema, replication and mutli-DMS gem for ActiveRecord.
|
3
|
+
|
4
|
+
## Features
|
5
|
+
* Threadsafe connection switching
|
6
|
+
* Replication can be defined in the database.yml or in the model, adds #slaves and #masters as ActiveRecord::Relations.
|
7
|
+
* Automatically builds connection classes, if configured.
|
13
8
|
|
14
9
|
## Installation
|
15
10
|
|
@@ -31,129 +26,152 @@ Run bundle install:
|
|
31
26
|
|
32
27
|
common: &common
|
33
28
|
adapter: mysql2
|
34
|
-
username: root
|
35
|
-
password: *****
|
36
29
|
pool: 20
|
37
|
-
|
38
|
-
timeout: 900
|
30
|
+
reconnect: true
|
39
31
|
socket: /tmp/mysql.sock
|
40
|
-
build_connection_class: true
|
41
|
-
|
42
|
-
development:
|
43
|
-
<<: *common
|
44
|
-
database: test_app
|
45
|
-
slaves: [slave_1_test_app_development, slave_2_test_app_development]
|
46
|
-
|
47
|
-
slave_1_test_app_development:
|
48
|
-
<<: *common
|
49
|
-
database: test_app
|
50
|
-
readonly: true
|
51
32
|
|
52
|
-
|
33
|
+
production:
|
53
34
|
<<: *common
|
54
|
-
database:
|
55
|
-
|
56
|
-
|
57
|
-
|
35
|
+
database: myapp
|
36
|
+
host: <%=ENV['DB_HOST']%>
|
37
|
+
username: <%=ENV['DB_USER']%>
|
38
|
+
password: <%=ENV['DB_PASS']%>
|
39
|
+
slaves: [slave_1_production, slave_2_production]
|
40
|
+
build_connection_class: true
|
41
|
+
|
42
|
+
slave_1_production:
|
58
43
|
<<: *common
|
59
|
-
|
60
|
-
|
44
|
+
host: <%=ENV['SLAVE_1_DB_HOST']%>
|
45
|
+
username: <%=ENV['SLAVE_1_DB_USER']%>
|
46
|
+
password: <%=ENV['SLAVE_1_DB_PASS']%>
|
47
|
+
database: myapp
|
48
|
+
build_connection_class: true
|
61
49
|
|
62
|
-
|
50
|
+
slave_2_production:
|
63
51
|
<<: *common
|
64
|
-
|
65
|
-
|
52
|
+
host: <%=ENV['SLAVE_2_DB_HOST']%>
|
53
|
+
username: <%=ENV['SLAVE_2_DB_USER']%>
|
54
|
+
password: <%=ENV['SLAVE_2_DB_PASS']%>
|
55
|
+
database: myapp
|
56
|
+
build_connection_class: true
|
66
57
|
|
67
|
-
|
58
|
+
foo_data_production
|
68
59
|
<<: *common
|
60
|
+
host: <%=ENV['USER_DATA_DB_HOST']%>
|
61
|
+
username: <%=ENV['USER_DATA_DB_USER']%>
|
62
|
+
password: <%=ENV['USER_DATA_DB_PASS']%>
|
69
63
|
database: user_data
|
70
|
-
|
64
|
+
build_connection_class: true
|
71
65
|
|
72
66
|
In the above database.yml the Master databases are listed as "development" and "user_data_development".
|
73
|
-
Replication databases are defined as normally connections and are added to the
|
74
|
-
their master.
|
75
|
-
|
67
|
+
Replication databases are defined as normally connections and are added to the `replication_connections` for
|
68
|
+
their master.
|
76
69
|
|
77
70
|
## Building Connection Classes
|
78
71
|
|
79
72
|
### Manually
|
80
|
-
ConnectionManager provides establish_managed_connection for build connection
|
81
|
-
classes and connection to multiple databases.
|
73
|
+
ConnectionManager provides `establish_managed_connection` for build connection
|
74
|
+
classes and connection to multiple databases. The `establish_managed_connection method`,
|
75
|
+
runs `establish_connection` with the supplied database.yml key, sets `abstract_class` to true.
|
82
76
|
|
83
|
-
class
|
84
|
-
establish_managed_connection("
|
77
|
+
class MySlaveConnection < ActiveRecord::Base
|
78
|
+
establish_managed_connection("slave_1_#{Rails.env}")
|
85
79
|
end
|
86
80
|
|
87
|
-
class User <
|
88
|
-
end
|
81
|
+
class User < MySlaveConnection;end
|
89
82
|
|
90
|
-
MyConnection
|
83
|
+
MyConnection # => MyConnection(abstract)
|
91
84
|
@user = User.first
|
92
|
-
@user
|
93
|
-
|
94
|
-
The establish_managed_connection method, runs establish_connection with the supplied
|
95
|
-
database.yml key, sets abstract_class to true, and (since :readonly is set to true) ensures
|
96
|
-
all ActiveRecord objects build using this connection class are readonly. If readonly is set
|
97
|
-
to true in the database.yml, passing the readonly option is not necessary.
|
85
|
+
@user # => #<User id:1>
|
98
86
|
|
99
87
|
|
88
|
+
|
100
89
|
### Automatically
|
101
90
|
ActiveRecord can build all your connection classes for you.
|
102
|
-
The connection class names will be based on the database.yml keys.ActiveRecord will
|
91
|
+
The connection class names will be based on the database.yml keys. ActiveRecord will
|
103
92
|
build connection classes for all the entries in the database.yml where
|
104
|
-
|
93
|
+
`build_connection_class: = true`, and match the current environment settings
|
94
|
+
|
95
|
+
# Class names derived from YML keys
|
96
|
+
'production' = 'BaseConnection'
|
97
|
+
'slave_1_production' = 'Save1Connection'
|
98
|
+
'slave_2_production' = 'Save2Connection'
|
99
|
+
'foo_data_production' = 'FooDataConnection'
|
100
|
+
|
105
101
|
|
106
102
|
## Using
|
107
103
|
|
108
104
|
The using method allows you specify the connection class to use
|
109
|
-
for query.
|
110
|
-
class's superclass will be the connection class and all database actions performed
|
111
|
-
on the instance will use the connection class's connection.
|
105
|
+
for query.
|
112
106
|
|
113
107
|
User.using("Slave1Connection").first
|
114
108
|
|
115
109
|
search = User.where(disabled => true)
|
116
|
-
@legacy_users = search.using("
|
117
|
-
@legacy_users.first.save #=> uses the
|
110
|
+
@legacy_users = search.using("Slave1Connection").all # => [<User...>,<User...>]
|
111
|
+
@legacy_users.first.save #=> uses the SlaveConnection connection
|
118
112
|
|
119
113
|
@new_users = search.page(params[:page]).all => [<User...>,<User...>]
|
120
114
|
|
121
115
|
## Replication
|
122
116
|
|
123
|
-
|
117
|
+
ConnectionManager creates ActiveRecord::Relation methods :slaves and :masters.
|
118
|
+
|
119
|
+
If you specify your replication model in your database.yml there is nothing more you need to do. If you looking for more
|
120
|
+
granular control you describe the replication setup on a per-model level.
|
124
121
|
|
125
122
|
class User < UserDataConnection
|
126
123
|
has_one :job
|
127
124
|
has_many :teams
|
128
|
-
replicated
|
129
|
-
# model code ...
|
125
|
+
replicated :slaves => [MySlave1Connection, MySlave2Connection]
|
130
126
|
end
|
131
127
|
|
132
|
-
|
133
|
-
User::Slave1UserDataConnectionDup.superclass => Slave1UserDataConnection(abstract)
|
134
|
-
User::Slave1UserDataDup.first => returns results from slave_1_user_data_development
|
135
|
-
User::Slave2UserDataDup.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_user_data_development
|
128
|
+
User.limit(2).slaves.all # => [<User...>,<User...>] results from MySlave1Connection or MySlave2Connection
|
136
129
|
|
137
|
-
|
138
|
-
|
130
|
+
|
131
|
+
If there are multiple replication connections the system will pick a connection at random using Array#sample.
|
139
132
|
|
140
133
|
User.slaves.first => returns results from slave_1_use_data_development
|
141
|
-
User.slaves.last =>
|
142
|
-
User.slaves.where(
|
143
|
-
User.slaves.where(
|
134
|
+
User.slaves.last => returns results from slave_2_user_data_development
|
135
|
+
User.slaves.where('id BETWEEN ? and ?',1,100]).all => returns results from slave_1_user_data_development
|
136
|
+
User.slaves.where('id BETWEEN ? and ?',1,100]).all => returns results from slave_2_user_data_development
|
144
137
|
|
145
|
-
|
146
|
-
|
138
|
+
### Repliation with cross-schema queries
|
139
|
+
Setup replication as you would normally
|
147
140
|
|
148
|
-
|
149
|
-
|
150
|
-
|
141
|
+
Next build connection classes that inherit from you base connection classes for each of your schemas
|
142
|
+
EX
|
143
|
+
class UserSchema < ActiveRecord::Base
|
144
|
+
self.abstract_class = true
|
145
|
+
self.table_name_prefix = 'user_schema.'
|
146
|
+
|
147
|
+
def self.inherited(base)
|
148
|
+
base.use_schema(self.schema_name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class User < UserSchema
|
153
|
+
has_many :bars
|
154
|
+
end
|
155
|
+
|
156
|
+
class FooSchema < ActiveRecord::Base
|
157
|
+
self.abstract_class = true
|
158
|
+
self.table_name_prefix = 'foo.'
|
159
|
+
|
160
|
+
def self.inherited(base)
|
161
|
+
base.use_schema(self.schema_name)
|
162
|
+
end
|
151
163
|
end
|
152
164
|
|
165
|
+
class Bar < FooSchema
|
166
|
+
belongs_to :user
|
167
|
+
end
|
168
|
+
|
169
|
+
User.joins(:bars).limit(1).to_sql # => SELECT * FROM `user_schema`.`users` INNER JOIN `foo.bars` ON `foo.bars`.`user_id` = `user_schema`.`users` LIMIT 1"
|
170
|
+
|
153
171
|
## Sharding
|
154
172
|
|
155
173
|
After tinkering with some solutions for shards, I've come to a similar conclusion as [DataFabric] (https://github.com/mperham/data_fabric):
|
156
|
-
"Sharding should be implemented at the application level". The
|
174
|
+
"Sharding should be implemented at the application level". The `shards` method is very basic and
|
157
175
|
while it may be useful to most folks, it should really serve as an example of a possible solutions
|
158
176
|
to your shard requirements.
|
159
177
|
|
@@ -176,7 +194,7 @@ originate from classes that used establish_connection you must surround your cod
|
|
176
194
|
Some queries...
|
177
195
|
}
|
178
196
|
|
179
|
-
In Rails
|
197
|
+
In Rails, you can create an around filter for your controllers
|
180
198
|
|
181
199
|
class ApplicationController < ActionController::Base
|
182
200
|
around_filter :cache_slaves
|
@@ -186,8 +204,7 @@ In Rails for less complicated schemas you could simply create an around filter f
|
|
186
204
|
end
|
187
205
|
|
188
206
|
## Migrations
|
189
|
-
|
190
|
-
Nothing implement now to help but there are lots of potential solutions [here] (http://stackoverflow.com/questions/1404620/using-rails-migration-on-different-database-than-standard-production-or-devel)
|
207
|
+
There are lots of potential solutions [here] (http://stackoverflow.com/questions/1404620/using-rails-migration-on-different-database-than-standard-production-or-devel)
|
191
208
|
|
192
209
|
## TODOs
|
193
210
|
* Maybe add migration support for Rails AR implementations.
|
data/connection_manager.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Joshua Mckinney"]
|
9
9
|
s.email = ["joshmckin@gmail.com"]
|
10
10
|
s.homepage = ""
|
11
|
-
s.summary = %q{
|
12
|
-
s.description = %q{
|
11
|
+
s.summary = %q{Cross-schema, replication and mutli-DMS gem for ActiveRecord.}
|
12
|
+
s.description = %q{Improves support for cross-schema, replication and mutli-DMS applications using ActiveRecord.}
|
13
13
|
|
14
14
|
s.rubyforge_project = "connection_manager"
|
15
15
|
|
@@ -18,16 +18,12 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
s.add_runtime_dependency 'activerecord', '>= 3.0', '<= 4.1'
|
21
|
-
s.
|
21
|
+
s.add_runtime_dependency 'activesupport', '>= 3.0', '<= 4.1'
|
22
|
+
s.add_runtime_dependency 'thread_safe'
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.0'
|
22
24
|
s.add_development_dependency 'autotest'
|
23
25
|
s.add_development_dependency 'mocha'
|
24
26
|
s.add_development_dependency 'factory_girl'
|
25
|
-
s.add_development_dependency '
|
26
|
-
|
27
|
-
if(defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE)
|
28
|
-
s.add_development_dependency 'jruby-openssl'
|
29
|
-
s.add_development_dependency 'activerecord-jdbcmysql-adapter'
|
30
|
-
else
|
31
|
-
s.add_development_dependency "mysql2"
|
32
|
-
end
|
27
|
+
s.add_development_dependency 'mysql2'
|
28
|
+
s.add_development_dependency 'pg'
|
33
29
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
class Builder
|
3
|
+
class << self
|
4
|
+
# Grab only those configurations that correspond to the current env (If env
|
5
|
+
# is blank it grabs all the connection keys) and where :build_connection_class is true
|
6
|
+
def database_keys_for_auto_build
|
7
|
+
ActiveRecord::Base.configurations.select{ |k,v| v[:build_connection_class] && k.match(env_regex)}.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
# Builds connection classes using the database keys provided; expects an array.
|
11
|
+
def build_connection_classes(database_keys_to_use=database_keys_for_auto_build)
|
12
|
+
database_keys_to_use.each do |key|
|
13
|
+
build_connection_class(connection_class_name(key),key.to_sym)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Build connection classes base on the supplied class name and connection
|
18
|
+
# key from database.yml
|
19
|
+
def build_connection_class(class_name,connection_key)
|
20
|
+
begin
|
21
|
+
class_name.constantize
|
22
|
+
rescue NameError
|
23
|
+
klass = Class.new(ActiveRecord::Base)
|
24
|
+
new_connection_class = Object.const_set(class_name, klass)
|
25
|
+
new_connection_class.abstract_class = true
|
26
|
+
new_connection_class.establish_connection(connection_key.to_sym)
|
27
|
+
ConnectionManager.logger.info "Connection::Manager built: #{class_name} for #{connection_key}" if ConnectionManager.logger
|
28
|
+
new_connection_class
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def env_regex
|
33
|
+
return @env_regex if @env_regex
|
34
|
+
s = "#{ConnectionManager.env}$"
|
35
|
+
@env_regex = Regexp.new("(#{s})")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
# Creates a string to be used for the class name. Removes the current env.
|
40
|
+
def clean_yml_key(name)
|
41
|
+
new_name = "#{name}".gsub(env_regex,'')
|
42
|
+
new_name = "Base" if new_name.blank?
|
43
|
+
new_name.gsub(/\_$/,'')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Given an connection key name from the database.yml, returns the string
|
47
|
+
# equivalent of the class name for that entry.
|
48
|
+
def connection_class_name(name_from_yml)
|
49
|
+
new_class_name = clean_yml_key(name_from_yml)
|
50
|
+
new_class_name = new_class_name.gsub(/\_/,' ').titleize.gsub(/ /,'')
|
51
|
+
new_class_name << "Connection"
|
52
|
+
new_class_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module AbstractAdapter
|
3
|
+
def config
|
4
|
+
@config
|
5
|
+
end
|
6
|
+
# Determines if connection supports cross database queries
|
7
|
+
def cross_schema_support?
|
8
|
+
@cross_schema_support ||= (config[:adapter].match(/(mysql)|(postgres)|(sqlserver)/i))
|
9
|
+
end
|
10
|
+
alias :cross_database_support? :cross_schema_support?
|
11
|
+
|
12
|
+
def mysql?
|
13
|
+
@is_mysql ||= (config[:adapter].match(/(mysql)/i))
|
14
|
+
end
|
15
|
+
|
16
|
+
def postgresql?
|
17
|
+
@is_postgresql ||= (config[:adapter].match(/(postgres)/i))
|
18
|
+
end
|
19
|
+
|
20
|
+
def sqlserver?
|
21
|
+
@is_sqlserver ||= (config[:adapter].match(/(sqlserver)/i))
|
22
|
+
end
|
23
|
+
|
24
|
+
def replicated?
|
25
|
+
(!slave_keys.blank? || !master_keys.blank?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def database_name
|
29
|
+
config[:database]
|
30
|
+
end
|
31
|
+
|
32
|
+
def slave_keys
|
33
|
+
@slave_keys ||= (config[:slaves] ? config[:slaves].collect{|r| r.to_sym} : [] )
|
34
|
+
end
|
35
|
+
|
36
|
+
def master_keys
|
37
|
+
@master_keys ||= (config[:masters] ? config[:masters].collect{|r| r.to_sym} : [])
|
38
|
+
end
|
39
|
+
|
40
|
+
def replication_keys(type=:slaves)
|
41
|
+
return slave_keys if type == :slaves
|
42
|
+
master_keys
|
43
|
+
end
|
44
|
+
|
45
|
+
def replications
|
46
|
+
{:slaves => slave_keys, :masters => master_keys}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include,(ConnectionManager::AbstractAdapter))
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module MysqlAdapter
|
3
|
+
|
4
|
+
# Force all tables to be cached for the life connection
|
5
|
+
def cached_tables
|
6
|
+
@cached_tables ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def new_tables(name = nil, database = nil, like =nil)
|
10
|
+
return cached_tables[database] if cached_tables[database] && like.nil?
|
11
|
+
cached_tables[database] ||= []
|
12
|
+
return [like] if like && cached_tables[database].include?(like)
|
13
|
+
sql = "SHOW TABLES "
|
14
|
+
sql << "IN #{database} " if database
|
15
|
+
sql << "LIKE #{quote(like)}" if like
|
16
|
+
result = execute(sql, 'SCHEMA')
|
17
|
+
cached_tables[database] = (cached_tables[database] | result.collect { |field| field[0] }).compact
|
18
|
+
end
|
19
|
+
alias :tables :new_tables
|
20
|
+
|
21
|
+
|
22
|
+
# We have to clean the name of '`' and fetch table name with schema
|
23
|
+
def table_exists?(name)
|
24
|
+
return false unless name
|
25
|
+
name = name.to_s
|
26
|
+
schema, table = name.split('.', 2)
|
27
|
+
unless table # A table was provided without a schema
|
28
|
+
table = schema
|
29
|
+
schema = nil
|
30
|
+
end
|
31
|
+
new_tables(nil, schema, table).include?(table)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR <= 1
|
36
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
37
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include,(ConnectionManager::MysqlAdapter)) if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
|
38
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:include,(ConnectionManager::MysqlAdapter))
|
39
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'active_record/relation'
|
3
|
+
module ConnectionManager
|
4
|
+
module ConnectionHandling
|
5
|
+
@@managed_connections = ThreadSafe::Cache.new
|
6
|
+
|
7
|
+
# Attempts to return the schema from table_name and table_name_prefix
|
8
|
+
def schema_name
|
9
|
+
return self.table_name.split('.')[0] if self.table_name && self.table_name.match(/\./)
|
10
|
+
return self.table_name_prefix.to_s.gsub(/\./,'') if self.table_name_prefix && self.table_name_prefix.match(/\./)
|
11
|
+
return self.connection.config[:database] if self.connection.mysql?
|
12
|
+
end
|
13
|
+
alias :database_name :schema_name
|
14
|
+
|
15
|
+
# Set the unformatted schema name for the given model / connection class
|
16
|
+
# EX: class User < ActiveRecord::Base
|
17
|
+
# self.schema_name = 'users_db'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# User.schema_name # => 'users_db'
|
21
|
+
# User.table_name_prefix # => 'users_db.'
|
22
|
+
# User.table_name # => 'users_db.users'
|
23
|
+
def schema_name=schema_name
|
24
|
+
self.table_name_prefix = "#{schema_name}." if schema_name && schema_name.to_s != ""
|
25
|
+
self.table_name = "#{self.table_name_prefix}#{self.table_name}" unless self.abstract_class?
|
26
|
+
end
|
27
|
+
alias :database_name= :schema_name=
|
28
|
+
|
29
|
+
# A place to store managed connections
|
30
|
+
def managed_connections
|
31
|
+
@@managed_connections
|
32
|
+
end
|
33
|
+
|
34
|
+
def managed_connection_classes
|
35
|
+
managed_connections.values.flatten
|
36
|
+
end
|
37
|
+
|
38
|
+
# Establishes and checks in a connection, normally for abstract classes AKA connection classes.
|
39
|
+
#
|
40
|
+
# Options:
|
41
|
+
# * :abstract_class - used the set #abstract_class, default is true
|
42
|
+
# * :schema_name - the unformatted schema name for connection, is inherited but child classes
|
43
|
+
# * :table_name - the table name for the model, should not be used on abstract classes
|
44
|
+
#
|
45
|
+
# EX:
|
46
|
+
# class MyConnection < ActiveRecord::Base
|
47
|
+
# establish_managed_connection :key_from_db_yml,{:readonly => true}
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
def establish_managed_connection(yml_key,opts={})
|
51
|
+
opts = {:class_name => self.name,
|
52
|
+
:abstract_class => true}.merge(opts)
|
53
|
+
establish_connection(yml_key)
|
54
|
+
self.abstract_class = opts[:abstract_class]
|
55
|
+
self.table_name = opts[:table_name] if opts[:table_name]
|
56
|
+
if (opts[:schema_name] || opts[:database_name])
|
57
|
+
self.schema_name = (opts[:schema_name] || opts[:database_name])
|
58
|
+
else
|
59
|
+
self.schema_name = self.schema_name unless !self.connection.cross_schema_support?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.included(base)
|
64
|
+
base.alias_method_chain :establish_connection, :managed_connections
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.extended(base)
|
68
|
+
class << base
|
69
|
+
self.alias_method_chain :establish_connection, :managed_connections
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def establish_connection_with_managed_connections(spec = nil)
|
74
|
+
result = establish_connection_without_managed_connections(spec)
|
75
|
+
if spec && (spec.is_a?(Symbol) || spec.is_a?(String))
|
76
|
+
self.managed_connections[spec.to_sym] = self.name
|
77
|
+
elsif spec.nil? && ConnectionManager.env
|
78
|
+
self.managed_connections[ConnectionManager.env.to_sym] = self.name
|
79
|
+
else
|
80
|
+
self.managed_connections[self.name] = self.name
|
81
|
+
end
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
87
|
+
ActiveRecord::ConnectionHandling.send(:include, ConnectionManager::ConnectionHandling)
|
88
|
+
else
|
89
|
+
require 'active_record/base'
|
90
|
+
ActiveRecord::Base.extend(ConnectionManager::ConnectionHandling)
|
91
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module Core
|
3
|
+
# We want to make sure we get the full table name with schema
|
4
|
+
def arel_table_with_check_name # :nodoc:
|
5
|
+
@arel_table = Arel::Table.new(table_name, arel_engine) unless (@arel_table && (@arel_table.name == self.table_name))
|
6
|
+
@arel_table
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.alias_method_chain :arel_table, :check_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.extended(base)
|
14
|
+
class << base
|
15
|
+
self.alias_method_chain :arel_table, :check_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
21
|
+
ActiveRecord::Core::ClassMethods.send(:include,ConnectionManager::Core)
|
22
|
+
else
|
23
|
+
ActiveRecord::Base.extend ConnectionManager::Core
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
module ConnectionManager
|
3
|
+
module Querying
|
4
|
+
delegate :using, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
|
5
|
+
delegate :slaves, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
|
6
|
+
delegate :masters, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
10
|
+
ActiveRecord::Querying.send(:include, ConnectionManager::Querying)
|
11
|
+
else
|
12
|
+
ActiveRecord::Base.send(:extend, ConnectionManager::Querying)
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer "connection_manager.build_connection_classes" do
|
4
|
+
require 'connection_manager/connection_adapters/mysql_adapter' if (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR <= 1 && (defined?(Mysql2::VERSION) || defined?(Mysql2::VERSION)))
|
5
|
+
ConnectionManager.env = Rails.env
|
6
|
+
ConnectionManager.logger = Rails.logger
|
7
|
+
ConnectionManager::Builder.build_connection_classes(Rails.application.config.database_configuration.select{ |k,v| v['build_connection_class'] && k.match(ConnectionManager::Builder.env_regex)}.keys)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module Relation
|
3
|
+
# Specify connection class to used for query. For
|
4
|
+
# example:
|
5
|
+
#
|
6
|
+
# users = User.using(MySlaveConnection).first
|
7
|
+
def using(connection_class_name)
|
8
|
+
@klass = ConnectionManager::Using::Proxy.new(@klass,connection_class_name)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def slaves
|
13
|
+
using(@klass.send(:fetch_slave_connection))
|
14
|
+
end
|
15
|
+
|
16
|
+
def masters
|
17
|
+
using(@klass.send(:fetch_master_connection))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
ActiveRecord::Relation.send(:include, ConnectionManager::Relation)
|