connection_manager 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|