connection_manager 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +20 -0
- data/README.md +135 -0
- data/Rakefile +1 -0
- data/connection_manager.gemspec +24 -0
- data/lib/connection_manager.rb +9 -0
- data/lib/connection_manager/associations.rb +29 -0
- data/lib/connection_manager/connection_manager_railtie.rb +9 -0
- data/lib/connection_manager/connections.rb +110 -0
- data/lib/connection_manager/replication_builder.rb +135 -0
- data/lib/connection_manager/version.rb +4 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +13 -0
- data/test_app/.gitignore +15 -0
- data/test_app/.rspec +1 -0
- data/test_app/Gemfile +14 -0
- data/test_app/Gemfile.lock +140 -0
- data/test_app/README +261 -0
- data/test_app/Rakefile +7 -0
- data/test_app/app/models/.gitkeep +0 -0
- data/test_app/app/models/basket.rb +4 -0
- data/test_app/app/models/fruit.rb +6 -0
- data/test_app/app/models/fruit_basket.rb +4 -0
- data/test_app/app/models/region.rb +3 -0
- data/test_app/app/models/type.rb +2 -0
- data/test_app/config.ru +4 -0
- data/test_app/config/application.rb +52 -0
- data/test_app/config/boot.rb +6 -0
- data/test_app/config/database.yml +42 -0
- data/test_app/config/environment.rb +5 -0
- data/test_app/config/environments/development.rb +30 -0
- data/test_app/config/environments/production.rb +60 -0
- data/test_app/config/environments/test.rb +39 -0
- data/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test_app/config/initializers/inflections.rb +10 -0
- data/test_app/config/initializers/load_connection_manager.rb +6 -0
- data/test_app/config/initializers/mime_types.rb +5 -0
- data/test_app/config/initializers/secret_token.rb +7 -0
- data/test_app/config/initializers/session_store.rb +8 -0
- data/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/test_app/config/locales/en.yml +5 -0
- data/test_app/config/routes.rb +58 -0
- data/test_app/db/migrate/20111127040654_create_fruits.rb +9 -0
- data/test_app/db/migrate/20111127040720_create_baskets.rb +9 -0
- data/test_app/db/migrate/20111127040846_create_fruit_baskets.rb +9 -0
- data/test_app/db/migrate/20111127040915_create_regions.rb +9 -0
- data/test_app/db/migrate/20111127060322_create_types.rb +9 -0
- data/test_app/db/schema.rb +16 -0
- data/test_app/db/seeds.rb +7 -0
- data/test_app/log/.gitkeep +0 -0
- data/test_app/script/rails +6 -0
- data/test_app/spec/connection_manager/associations_spec.rb +29 -0
- data/test_app/spec/connection_manager/connections_spec.rb +51 -0
- data/test_app/spec/connection_manager/replication_builder_spec.rb +33 -0
- data/test_app/spec/factories/baskets.rb +7 -0
- data/test_app/spec/factories/fruit_baskets.rb +8 -0
- data/test_app/spec/factories/fruits.rb +8 -0
- data/test_app/spec/factories/regions.rb +7 -0
- data/test_app/spec/factories/types.rb +7 -0
- data/test_app/spec/models/type_spec.rb +5 -0
- data/test_app/spec/spec.opts +4 -0
- data/test_app/spec/spec_helper.rb +42 -0
- data/test_app/test_app +0 -0
- metadata +158 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
activerecord-connection_manager (0.0.1)
|
5
|
+
activerecord (~> 3.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ZenTest (4.6.2)
|
11
|
+
activemodel (3.1.3)
|
12
|
+
activesupport (= 3.1.3)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
i18n (~> 0.6)
|
15
|
+
activerecord (3.1.3)
|
16
|
+
activemodel (= 3.1.3)
|
17
|
+
activesupport (= 3.1.3)
|
18
|
+
arel (~> 2.2.1)
|
19
|
+
tzinfo (~> 0.3.29)
|
20
|
+
activesupport (3.1.3)
|
21
|
+
multi_json (~> 1.0)
|
22
|
+
arel (2.2.1)
|
23
|
+
autotest (4.4.6)
|
24
|
+
ZenTest (>= 4.4.1)
|
25
|
+
builder (3.0.0)
|
26
|
+
diff-lcs (1.1.3)
|
27
|
+
i18n (0.6.0)
|
28
|
+
metaclass (0.0.1)
|
29
|
+
mocha (0.10.0)
|
30
|
+
metaclass (~> 0.0.1)
|
31
|
+
multi_json (1.0.3)
|
32
|
+
rspec (2.7.0)
|
33
|
+
rspec-core (~> 2.7.0)
|
34
|
+
rspec-expectations (~> 2.7.0)
|
35
|
+
rspec-mocks (~> 2.7.0)
|
36
|
+
rspec-core (2.7.1)
|
37
|
+
rspec-expectations (2.7.0)
|
38
|
+
diff-lcs (~> 1.1.2)
|
39
|
+
rspec-mocks (2.7.0)
|
40
|
+
tzinfo (0.3.31)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
activerecord-connection_manager!
|
47
|
+
autotest
|
48
|
+
mocha
|
49
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Joshua T. Mckinney
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# connection_manager
|
2
|
+
Replication and Multi-Database ActiveRecord add on.
|
3
|
+
|
4
|
+
## Goals
|
5
|
+
* Take the lib I've been using finally make something out of it ;)
|
6
|
+
* Use connection classes instead of establish_connection on every model to ensure connection pooling
|
7
|
+
* Focus connection management at the model level
|
8
|
+
* Use the default database.yml as single point for all database configurations (no extra .yml files)
|
9
|
+
* When slave objects are used in html helpers like link_to and form_for the created urls that match the master
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
connection_manager is available through [Rubygems](https://rubygems.org/gems/connection_manager) and can be installed via:
|
14
|
+
|
15
|
+
$ gem install connection_manager
|
16
|
+
|
17
|
+
## Rails 3 setup (Rails 2 untested at this time please let me know if it works for you )
|
18
|
+
|
19
|
+
connection_manager assumes the primary connection for the model is the master. For standard
|
20
|
+
models using the default connection this means the main rails database connection is the master.
|
21
|
+
|
22
|
+
Example database.yml
|
23
|
+
|
24
|
+
common: &common
|
25
|
+
adapter: mysql2
|
26
|
+
username: root
|
27
|
+
password: *****
|
28
|
+
database_timezone: local
|
29
|
+
pool: 100
|
30
|
+
connect_timeout: 20
|
31
|
+
timeout: 900
|
32
|
+
socket: /tmp/mysql.sock
|
33
|
+
|
34
|
+
development:
|
35
|
+
<<: *common
|
36
|
+
database: test_app
|
37
|
+
|
38
|
+
slave_1_test_app_development:
|
39
|
+
<<: *common
|
40
|
+
database: portal_production
|
41
|
+
|
42
|
+
slave_2_test_app_development:
|
43
|
+
<<: *common
|
44
|
+
database: test_app
|
45
|
+
|
46
|
+
user_data_development
|
47
|
+
<<: *common
|
48
|
+
database: user_data
|
49
|
+
|
50
|
+
slave_1_user_data_development
|
51
|
+
<<: *common
|
52
|
+
database: user_data
|
53
|
+
|
54
|
+
slave_2_user_data_development
|
55
|
+
<<: *common
|
56
|
+
database: user_data
|
57
|
+
|
58
|
+
In the above database.yml the Master databases are listed as "development" and "user_data_development".
|
59
|
+
As you can see the replication database name follow a strict standard for the connection names.
|
60
|
+
For slave_1_test_app_development, "slave" is the name of the replication, "1" is the count, "test_app"
|
61
|
+
is the databases name and finally the "development" is the environment. (Of course in your database.yml
|
62
|
+
each slave would have a different connection to is replication :)
|
63
|
+
|
64
|
+
|
65
|
+
### Setup Multiple Databases
|
66
|
+
|
67
|
+
At startup connection_manager builds connection classes to ConnectionManager::Connections
|
68
|
+
using the connections described in your database.yml based on the current rails environment.
|
69
|
+
|
70
|
+
You can use a different master by having the model inherit from one of your ConnectionManager::Connections.
|
71
|
+
|
72
|
+
To view your ConnectionManager::Connections, at the Rails console type:
|
73
|
+
|
74
|
+
ruby-1.9.2-p290 :001 > ConnectionManager::Connections.all => ["TestAppConnection", "Slave1TestAppConnection", "Slave2TestAppConnection"]
|
75
|
+
|
76
|
+
If your using the example database.yml your array would look like this:
|
77
|
+
["TestAppConnection", "Slave1TestAppConnection", "Slave2TestAppConnection",
|
78
|
+
"UserDataConnection", "Slave1UserDataConnection", "Slave2UserDataConnection"]
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
To use one of your ConnectionManager::Connections for your models default/master database
|
83
|
+
setup you model like the following
|
84
|
+
|
85
|
+
class User < ConnectionManager::Connections::UserDataConnection
|
86
|
+
# model code ...
|
87
|
+
end
|
88
|
+
|
89
|
+
### Replication
|
90
|
+
|
91
|
+
simply add 'replicated' to you model beneath any defined associations
|
92
|
+
|
93
|
+
class User < ConnectionManager::Connections::UserDataConnection
|
94
|
+
has_one :job
|
95
|
+
has_many :teams
|
96
|
+
replicated # implement replication
|
97
|
+
# model code ...
|
98
|
+
end
|
99
|
+
|
100
|
+
The replicated method addeds subclass whose names match the replication connection name and count.
|
101
|
+
Based on the above example database.yml User class would now have User::Slave1 and User::Slave2.
|
102
|
+
|
103
|
+
You can treat your subclass like normal activerecord objects.
|
104
|
+
User::Slave1.first => returns results from slave_1_use_data_development
|
105
|
+
User::Slave2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_use_data_development
|
106
|
+
|
107
|
+
For a more elegant implementation, connection_manager also add class methods to you main model following the
|
108
|
+
same naming standard as the subclass creation.
|
109
|
+
User.slave_1.first => returns results from slave_1_use_data_development
|
110
|
+
User.slave_2.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_use_data_development
|
111
|
+
|
112
|
+
Finally connection_manager creates an addional class method that shifts through your
|
113
|
+
available slave connections each time it is called using a different connection on each action.
|
114
|
+
User.slave.first => returns results from slave_1_use_data_development
|
115
|
+
User.slave.last => => returns results from slave_2_use_data_development
|
116
|
+
User.slave.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_1_use_data_development
|
117
|
+
User.slave.where(['created_at BETWEEN ? and ?',Time.now - 5.days, Time.now]).all => returns results from slave_2_use_data_development
|
118
|
+
|
119
|
+
## TODO's
|
120
|
+
* add more to readme
|
121
|
+
* more specs
|
122
|
+
* sharding
|
123
|
+
|
124
|
+
## Other activerecord Connection gems
|
125
|
+
* [Octopus](https://github.com/tchandy/octopus)
|
126
|
+
|
127
|
+
## Contributing to connection_manager
|
128
|
+
|
129
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
130
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
131
|
+
* Fork the project
|
132
|
+
* Start a feature/bugfix branch
|
133
|
+
* Commit and push until you are happy with your contribution
|
134
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
135
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "connection_manager/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "connection_manager"
|
7
|
+
s.version = ConnectionManager::VERSION
|
8
|
+
s.authors = ["Joshua Mckinney"]
|
9
|
+
s.email = ["joshmckin@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Simplifies connecting to Muliple and Replication databases with rails and active_record}
|
12
|
+
s.description = %q{Simplifies connecting to Muliple and Replication databases with rails and active_record}
|
13
|
+
|
14
|
+
s.rubyforge_project = "connection_manager"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.add_runtime_dependency 'activerecord', '~> 3.0'
|
21
|
+
s.add_development_dependency 'rspec'
|
22
|
+
s.add_development_dependency 'autotest'
|
23
|
+
s.add_development_dependency 'mocha'
|
24
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "connection_manager/version"
|
2
|
+
|
3
|
+
module ConnectionManager
|
4
|
+
require 'connection_manager/connections'
|
5
|
+
require 'connection_manager/associations'
|
6
|
+
require 'connection_manager/replication_builder'
|
7
|
+
require 'connection_manager/connection_manager_railtie.rb' if defined?(Rails)
|
8
|
+
end
|
9
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module Associations
|
3
|
+
@defined_associations
|
4
|
+
|
5
|
+
# Stores defined associtions and their options
|
6
|
+
def defined_associations
|
7
|
+
@defined_associations ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def belongs_to(*options)
|
11
|
+
defined_associations[:belongs_to] ||= []
|
12
|
+
defined_associations[:belongs_to] << options
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_many(*options)
|
17
|
+
defined_associations[:has_many] ||= []
|
18
|
+
defined_associations[:has_many] << options
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_one(*options)
|
23
|
+
defined_associations[:has_one] ||= []
|
24
|
+
defined_associations[:has_one] << options
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
ActiveRecord::Base.extend(ConnectionManager::Associations)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
class Connections
|
3
|
+
class << self
|
4
|
+
@connection_keys
|
5
|
+
@all
|
6
|
+
@replication_connections
|
7
|
+
@env
|
8
|
+
|
9
|
+
def env
|
10
|
+
@env ||= fetch_env
|
11
|
+
@env
|
12
|
+
end
|
13
|
+
|
14
|
+
def env=env
|
15
|
+
@env = env
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the current Rails environment if defined
|
19
|
+
# TODO add sinatra
|
20
|
+
def fetch_env
|
21
|
+
Rails.env if defined?(Rails)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Grab only thoses connections that correspond to the current env. If env
|
25
|
+
# is blank it grabs all the connection keys
|
26
|
+
def connection_keys
|
27
|
+
@connection_keys ||= ActiveRecord::Base.configurations.keys.
|
28
|
+
select{|n| n.match(Regexp.new("(#{env}$)"))}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Contains all the connection classes built
|
32
|
+
def all
|
33
|
+
@all ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Holds connections
|
37
|
+
def replication_connections
|
38
|
+
@replication_connections ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the database value given a connection key from the database.yml
|
42
|
+
def database_name_from_yml(name_from_yml)
|
43
|
+
ActiveRecord::Base.configurations[name_from_yml]['database'].to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
# Given an connection key name from the database.yml, returns the string
|
47
|
+
# equivelent of the class name for that entry.
|
48
|
+
def connection_class_name(name_from_yml)
|
49
|
+
name_from_yml = name_from_yml #clean up sqlite database names
|
50
|
+
new_class_name = name_from_yml.gsub(Regexp.new("#{env}$"),'')
|
51
|
+
new_class_name = database_name_from_yml(name_from_yml) if new_class_name.blank?
|
52
|
+
|
53
|
+
#cleanup sqlite database names
|
54
|
+
if new_class_name.gsub!(/(^db\/)|(\.sqlite3$)/,'')
|
55
|
+
new_class_name.gsub!(Regexp.new("(\\_#{env}$)"),'')
|
56
|
+
end
|
57
|
+
|
58
|
+
new_class_name = new_class_name.gsub(/\_/,' ').titleize.gsub(/ /,'')
|
59
|
+
new_class_name << "Connection"
|
60
|
+
new_class_name
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
def add_replication_connection(name_from_yml,new_connection)
|
66
|
+
rep_name = name_from_yml.split("_")[0]
|
67
|
+
db_name = database_name_from_yml(name_from_yml)
|
68
|
+
if db_name.gsub!(/(^db\/)|(\.sqlite3$)/,'')
|
69
|
+
db_name.gsub!(Regexp.new("(\\_#{env}$)"),'')
|
70
|
+
end
|
71
|
+
rep_name << "_#{db_name}"
|
72
|
+
rep_name = rep_name.to_sym
|
73
|
+
replication_connections[rep_name] ||= []
|
74
|
+
replication_connections[rep_name] << new_connection
|
75
|
+
replication_connections
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def connections_for_replication(rep_collection_key)
|
80
|
+
replication_connections[rep_collection_key.to_sym]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sets class instance attributes, then builds connection classes, while populating
|
84
|
+
# available_connctions and replication_connection
|
85
|
+
def initialize(options={})
|
86
|
+
options.each do |k,v|
|
87
|
+
send("#{k.to_s}=",v)
|
88
|
+
end
|
89
|
+
connection_keys.each do |connection|
|
90
|
+
new_connection = connection_class_name(connection)
|
91
|
+
add_replication_connection(connection,new_connection)
|
92
|
+
build_connection_class(new_connection,connection)
|
93
|
+
end
|
94
|
+
all
|
95
|
+
end
|
96
|
+
|
97
|
+
# Addes a conneciton subclass to AvailableConnections using the supplied
|
98
|
+
# class name and connection key from database.yml
|
99
|
+
def build_connection_class(class_name,connection_key)
|
100
|
+
class_eval <<-STR, __FILE__, __LINE__
|
101
|
+
class #{class_name} < ActiveRecord::Base
|
102
|
+
self.abstract_class = true
|
103
|
+
establish_connection("#{connection_key}")
|
104
|
+
end
|
105
|
+
STR
|
106
|
+
all << class_name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module ReplicationBuilder
|
3
|
+
|
4
|
+
def database_name(db_name=nil)
|
5
|
+
db_name = "#{connection.instance_variable_get(:@config)[:database].to_s}" if db_name.blank?
|
6
|
+
db_name.gsub!(/(\.sqlite3$)/,'')
|
7
|
+
db_name = db_name.split("/").last
|
8
|
+
db_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def replication_association_options(method,association,class_name,options={})
|
12
|
+
new_options = {}.merge(options)
|
13
|
+
if new_options[:class_name].blank?
|
14
|
+
new_options[:class_name] = "#{association.to_s.singularize.classify}::#{class_name}"
|
15
|
+
else
|
16
|
+
new_options[:class_name] = "#{new_options[:class_name]}::#{class_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
if [:has_one,:has_many].include?(method) && new_options[:foreign_key].blank?
|
20
|
+
new_options[:foreign_key] = "#{table_name.singularize}_id"
|
21
|
+
end
|
22
|
+
new_options
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_replication_associations(class_name)
|
26
|
+
str = ""
|
27
|
+
defined_associations.each do |method,defs|
|
28
|
+
unless defs.blank?
|
29
|
+
defs.each do |association,options|
|
30
|
+
options = {} if options.blank?
|
31
|
+
unless options[:no_readonly] || options[:class_name].to_s.match("::#{class_name}")
|
32
|
+
str << "#{method.to_s} :#{association}, #{replication_association_options(method,association,class_name,options)};"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
str
|
38
|
+
end
|
39
|
+
|
40
|
+
def replication_connection_classes(options)
|
41
|
+
if options[:using] && options[:using].is_a?(Array)
|
42
|
+
connection_classes = options[:using].collection{|c| Connections.connection_class_name(c)}
|
43
|
+
else
|
44
|
+
connection_classes = Connections.connections_for_replication("#{options[:name].to_s}_#{database_name}")
|
45
|
+
end
|
46
|
+
connection_classes
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds subclass with the class name of the type provided in the options, which
|
50
|
+
# defaults to 'slave' if blank, that uses the connection from a connection class.
|
51
|
+
# If :database option is blank?, replicated will assume the database.yml has
|
52
|
+
# slave connections defined as: slave_database_name_test or slave_1_database_name_test,
|
53
|
+
# where slave_1 is the replication instance, 'database_name' is the actual
|
54
|
+
# name of the database and '_test' is the Rails environment
|
55
|
+
def replicated(*settings)
|
56
|
+
options = {:name => "slave", :readonly => true}.merge(settings.extract_options!)
|
57
|
+
connection_classes = replication_connection_classes(options)
|
58
|
+
if connection_classes.blank?
|
59
|
+
raise ArgumentError, " a replication connection was not found. Check your database.yml."
|
60
|
+
else
|
61
|
+
connection_methods = []
|
62
|
+
connection_classes.each do |c|
|
63
|
+
under_scored = c.underscore
|
64
|
+
method_name = under_scored.split("_")[0]
|
65
|
+
method_name = method_name.insert(method_name.index(/\d/),"_")
|
66
|
+
class_name = method_name.classify
|
67
|
+
connection_methods << method_name.to_sym
|
68
|
+
build_replication_class(class_name,c,options)
|
69
|
+
build_single_replication_method(method_name,class_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
build_full_replication_method(options,connection_methods)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creats a subclass that inherets from the model. The default model_name
|
77
|
+
# class method is overriden to return the super's name, which ensures rails
|
78
|
+
# helpers like link_to called on a replication stance generate a url for the
|
79
|
+
# master database. If options include readonly, build_replication_class also
|
80
|
+
# overrides the rails "readonly?" method to ensure saves are prevented.
|
81
|
+
# Replication class can be called directly for operaitons.
|
82
|
+
# Usage:
|
83
|
+
# => User::Slave1.where(:id => 1).first => returns results from slave_1 database
|
84
|
+
# => User::Slave2.where(:id => 2).first => returns results from slave_1 database
|
85
|
+
def build_replication_class(class_name,connection_name,options)
|
86
|
+
class_eval <<-STR, __FILE__, __LINE__
|
87
|
+
class #{class_name} < #{self.name}
|
88
|
+
#{build_replication_associations(class_name)}
|
89
|
+
class << self
|
90
|
+
delegate :connection, :to => Connections::#{connection_name}
|
91
|
+
def model_name
|
92
|
+
@_model_name ||= ActiveModel::Name.new(#{model_name})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
#{'def readonly?; true; end;' if (options[:name] == "readonly" || options[:readonly])}
|
96
|
+
end
|
97
|
+
STR
|
98
|
+
end
|
99
|
+
|
100
|
+
# Adds as class method to call a specific replication conneciton.
|
101
|
+
# Usage:
|
102
|
+
# => User.slave_1.where(:id => 2).first => returns results from slave_1 database
|
103
|
+
# => User.slave_2.where(:id => 2).first => returns results from slave_1 database
|
104
|
+
def build_single_replication_method(method_name,class_name)
|
105
|
+
class_eval <<-STR, __FILE__, __LINE__
|
106
|
+
class << self
|
107
|
+
def #{method_name}
|
108
|
+
self::#{class_name}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
STR
|
112
|
+
end
|
113
|
+
|
114
|
+
# add a class method that shifts through available connections methods
|
115
|
+
# on each call.
|
116
|
+
# Usage:
|
117
|
+
# => User.slave.where(:id => 2).first => can return results from slave_1 or slave_2
|
118
|
+
def build_full_replication_method(options,connection_methods)
|
119
|
+
class_eval <<-STR, __FILE__, __LINE__
|
120
|
+
@connection_methods = #{connection_methods}
|
121
|
+
class << self
|
122
|
+
def #{options[:name].to_s}
|
123
|
+
current = @connection_methods.shift
|
124
|
+
@connection_methods << current
|
125
|
+
send(current)
|
126
|
+
end
|
127
|
+
def connection_methods
|
128
|
+
@connection_methods
|
129
|
+
end
|
130
|
+
end
|
131
|
+
STR
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
ActiveRecord::Base.extend(ConnectionManager::ReplicationBuilder)
|