db-charmer 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/LICENSE +21 -0
- data/Makefile +2 -0
- data/README.rdoc +245 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/db-charmer.gemspec +57 -0
- data/init.rb +1 -0
- data/lib/db_charmer.rb +58 -0
- data/lib/db_charmer/active_record_extensions.rb +75 -0
- data/lib/db_charmer/association_proxy.rb +17 -0
- data/lib/db_charmer/connection_factory.rb +38 -0
- data/lib/db_charmer/connection_proxy.rb +11 -0
- data/lib/db_charmer/connection_switch.rb +29 -0
- data/lib/db_charmer/db_magic.rb +49 -0
- data/lib/db_charmer/finder_overrides.rb +47 -0
- data/lib/db_charmer/multi_db_migrations.rb +29 -0
- data/lib/db_charmer/multi_db_proxy.rb +50 -0
- metadata +82 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009, Alexey Kovyrin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Makefile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
= DB Charmer - ActiveRecord Connection Magic Plugin
|
2
|
+
|
3
|
+
+DbCharmer+ is a simple yet powerful plugin for ActiveRecord that does a few things:
|
4
|
+
|
5
|
+
1. Allows you to easily manage AR models' connections (+switch_connection_to+ method)
|
6
|
+
2. Allows you to switch AR models' default connections to a separate servers/databases
|
7
|
+
3. Allows you to easily choose where your query should go (<tt>Model.on_db</tt> methods)
|
8
|
+
4. Allows you to automatically send read queries to your slaves while masters would handle all the updates.
|
9
|
+
5. Adds multiple databases migrations to ActiveRecord
|
10
|
+
|
11
|
+
|
12
|
+
== Installation
|
13
|
+
|
14
|
+
There are two options when approaching db-charmer installation:
|
15
|
+
* using gem (recommended)
|
16
|
+
* install as a Rails plugin
|
17
|
+
|
18
|
+
To install as a gem, add this to your environment.rb:
|
19
|
+
|
20
|
+
config.gem 'kovyrin-db-charmer', :lib => 'db_charmer',
|
21
|
+
:source => 'http://gems.github.com'
|
22
|
+
|
23
|
+
And then run the command:
|
24
|
+
|
25
|
+
sudo rake gems:install
|
26
|
+
|
27
|
+
To install db-charmer as a Rails plugin use this:
|
28
|
+
|
29
|
+
script/plugin install git://github.com/kovyrin/db-charmer.git
|
30
|
+
|
31
|
+
|
32
|
+
== Easy ActiveRecord Connection Management
|
33
|
+
|
34
|
+
As a part of this plugin we've added +switch_connection_to+ method that accepts many different kinds
|
35
|
+
of db connections and uses them on a model. We support:
|
36
|
+
|
37
|
+
1. Strings and symbols as the names of connection configuration blocks in database.yml.
|
38
|
+
2. ActiveRecord models (we'd use connection currently set up on a model).
|
39
|
+
3. Database connections (<tt>Model.connection</tt>)
|
40
|
+
4. Nil values to reset model to default connection.
|
41
|
+
|
42
|
+
Sample code:
|
43
|
+
|
44
|
+
class Foo < ActiveRecord::Model; end
|
45
|
+
|
46
|
+
Foo.switch_connection_to(:blah)
|
47
|
+
Foo.switch_connection_to('foo')
|
48
|
+
Foo.switch_connection_to(Bar)
|
49
|
+
Foo.switch_connection_to(Baz.connection)
|
50
|
+
Foo.switch_connection_to(nil)
|
51
|
+
|
52
|
+
The +switch_connection_to+ method has an optional second parameter +should_exist+ which is true
|
53
|
+
by default. This parameter is used when the method is called with a string or a symbol connection
|
54
|
+
name and there is no such connection configuration in the database.yml file. If this parameter
|
55
|
+
is true, an exception would be raised, if it is false, the error would be ignored and no connection
|
56
|
+
change would happen. This is really useful when in development mode or in tests you do not want to
|
57
|
+
create many different databases on your local machine and just want to put all your tables in one
|
58
|
+
database.
|
59
|
+
|
60
|
+
Warning: All the connection switching calls would switch connection *only* for those classes the
|
61
|
+
method called on. You can't call the +switch_connection_to+ method and switch connection for a
|
62
|
+
base class in some hierarchy (for example, you can't switch AR::Base connection and see all your
|
63
|
+
models switched to the new connection, use classic +establish_connection+ instead).
|
64
|
+
|
65
|
+
|
66
|
+
== Multiple DB Migrations
|
67
|
+
|
68
|
+
In every application that works with many databases, there is need in convenient schema migrations mechanism.
|
69
|
+
|
70
|
+
All Rails users already have this mechanism - rails migrations. So in +DbCharmer+, we've made it possible
|
71
|
+
to seamlessly use multiple databases in Rails migrations.
|
72
|
+
|
73
|
+
There are two methods available in migrations to operate on more than one database:
|
74
|
+
|
75
|
+
1. Global connection change method - used to switch whole migration to a non-default database.
|
76
|
+
2. Block-level connection change method - could be used to do only a part of a migration on a non-default db.
|
77
|
+
|
78
|
+
Migration class example (global connection rewrite):
|
79
|
+
|
80
|
+
class MultiDbTest < ActiveRecord::Migration
|
81
|
+
db_magic :connection => :second_db
|
82
|
+
|
83
|
+
def self.up
|
84
|
+
create_table :test_table, :force => true do |t|
|
85
|
+
t.string :test_string
|
86
|
+
t.timestamps
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.down
|
91
|
+
drop_table :test_table
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Migration class example (block-level connection rewrite):
|
96
|
+
|
97
|
+
class MultiDbTest < ActiveRecord::Migration
|
98
|
+
def self.up
|
99
|
+
on_db :second_db do
|
100
|
+
create_table :test_table, :force => true do |t|
|
101
|
+
t.string :test_string
|
102
|
+
t.timestamps
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.down
|
108
|
+
on_db :second_db { drop_table :test_table }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
By default in development and test environments you could skip this <tt>:second_db</tt>
|
114
|
+
connection from your database.yml files, but in production you'd specify it and
|
115
|
+
get the table created on a separate server and/or in a separate database.
|
116
|
+
|
117
|
+
This behaviour is controlled by the <tt>DbCharmer.migration_connections_should_exist</tt>
|
118
|
+
configuration attribute which could be set from a rails initializer.
|
119
|
+
|
120
|
+
|
121
|
+
== Using Models in Master-Slave Environments
|
122
|
+
|
123
|
+
Master-slave replication is the most popular scale-out technique in medium and large
|
124
|
+
database applications today. There are some rails plugins out there that help rails
|
125
|
+
developers to use slave servers in their models but none of there were flexible enough
|
126
|
+
for us to start using them in a huge application we work on.
|
127
|
+
|
128
|
+
So, we've been using ActsAsReadonlyable plugin for a long time and have developed a
|
129
|
+
lots of additions to its code over that time. Since that plugin has been abandoned
|
130
|
+
by its authors, we've decided to collect all of our master-slave code in one plugin
|
131
|
+
and release it for rails 2.2+. +DbCharmer+ adds the following features to Rails models:
|
132
|
+
|
133
|
+
|
134
|
+
=== Auto-Switching all Reads to Slave(s)
|
135
|
+
|
136
|
+
When you create a model, you could use <tt>db_magic :slave => :blah</tt> or
|
137
|
+
<tt>db_magic :slaves => [ :foo, :bar ]</tt> commands in your model to set up reads
|
138
|
+
redirection mode when all your find/count/exist/etc methods will be reading data
|
139
|
+
from your slave (or a bunch of slaves in a round-robin manner). Here is an example:
|
140
|
+
|
141
|
+
class Foo < ActiveRecord::Base
|
142
|
+
db_magic :slave => :slave01
|
143
|
+
end
|
144
|
+
|
145
|
+
class Bar < ActiveRecord::Base
|
146
|
+
db_magic :slaves => [ :slave01, :slave02 ]
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
=== Default Connection Switching
|
151
|
+
|
152
|
+
If you have more than one master-slave cluster (or simply more than one database)
|
153
|
+
in your database environment, then you might want to change the default database
|
154
|
+
connection of some of your models. You could do that by using
|
155
|
+
<tt>db_magic :connection => :foo</tt> call from your models. Example:
|
156
|
+
|
157
|
+
class Foo < ActiveRecord::Base
|
158
|
+
db_magic :connection => :foo
|
159
|
+
end
|
160
|
+
|
161
|
+
Sample model on a separate master-slave cluster (so, separate main connection +
|
162
|
+
a slave connection):
|
163
|
+
|
164
|
+
class Bar < ActiveRecord::Base
|
165
|
+
db_magic :connection => :bar, :slave => :bar_slave
|
166
|
+
end
|
167
|
+
|
168
|
+
=== Per-Query Connection Management
|
169
|
+
|
170
|
+
Sometimes you have some select queries that you know you want to run on the master.
|
171
|
+
This could happen for example when you have just added some data and need to read
|
172
|
+
it back and not sure if it made it all the way to the slave yet or no. For this
|
173
|
+
situation an few others there are a few methods we've added to ActiveRecord models:
|
174
|
+
|
175
|
+
1) +on_master+ - this method could be used in two forms: block form and proxy form.
|
176
|
+
In the block form you could force connection switch for a block of code:
|
177
|
+
|
178
|
+
User.on_master do
|
179
|
+
user = User.find_by_login('foo')
|
180
|
+
user.update_attributes!(:activated => true)
|
181
|
+
end
|
182
|
+
|
183
|
+
In the proxy form this method could be used to force one query to be performed on
|
184
|
+
the master database server:
|
185
|
+
|
186
|
+
Comment.on_master.last(:limit => 5)
|
187
|
+
User.on_master.find_by_activation_code(code)
|
188
|
+
User.on_master.exists?(:login => login, :password => password)
|
189
|
+
|
190
|
+
2) +on_slave+ - this method is used to force a query to be run on a slave even in
|
191
|
+
situations when it's been previously forced to use the master. If there is more
|
192
|
+
than one slave, one would be selected randomly. Tis method has two forms as
|
193
|
+
well: block and proxy.
|
194
|
+
|
195
|
+
3) <tt>on_db(connection)</tt> - this method is what makes two previous methods possible.
|
196
|
+
It is used to switch a model's connection to some db for a short block of code
|
197
|
+
or even for one statement (two forms). It accepts the same range of values as
|
198
|
+
the +switch_connection_to+ method does. Example:
|
199
|
+
|
200
|
+
Comment.on_db(:olap).count
|
201
|
+
Post.on_db(:foo).find(:first)
|
202
|
+
|
203
|
+
|
204
|
+
=== Associations Connection Management
|
205
|
+
|
206
|
+
ActiveRecord models can have associations and with their own connections and it becomes
|
207
|
+
pretty hard to manage connections in chained calls like <tt>User.posts.count</tt>. With
|
208
|
+
class-only connection switching methods this call would look like the following if we'd
|
209
|
+
want to count posts on a separate database:
|
210
|
+
|
211
|
+
Post.on_db(:olap) { User.posts.count }
|
212
|
+
|
213
|
+
Apparently this is not the best way to write the code and we've implemented <tt>on_*</tt>
|
214
|
+
methods on associations as well so you could do things like this:
|
215
|
+
|
216
|
+
@user.posts.on_db(:olap).count
|
217
|
+
@user.posts.on_slave.find(:title => 'Hello, world!')
|
218
|
+
|
219
|
+
Notice: Since ActiveRecord associations implemented as proxies for resulting
|
220
|
+
objects/collections, it is possible to use our connection switching methods even without
|
221
|
+
chained methods:
|
222
|
+
|
223
|
+
@post.user.on_slave - would return post's author
|
224
|
+
@photo.owner.on_slave - would return photo's owner
|
225
|
+
|
226
|
+
|
227
|
+
== Documentation
|
228
|
+
|
229
|
+
For more information on the plugin internals, please check out the source code. All the plugin's
|
230
|
+
code is covered with tests that were placed in a separate staging rails project located at
|
231
|
+
http://github.com/kovyrin/db-charmer-sandbox. The project has unit tests for all or at least the
|
232
|
+
most of the parts of plugin's code.
|
233
|
+
|
234
|
+
|
235
|
+
== What Ruby and Rails implementations does it work for?
|
236
|
+
|
237
|
+
We've tested the plugin on MRI 1.8.6 with Rails 2.2 and 2.3. We use it in production on Scribd.com
|
238
|
+
with MRI 1.8.6 and Rails 2.2.
|
239
|
+
|
240
|
+
|
241
|
+
== Who are the authors?
|
242
|
+
|
243
|
+
This plugin has been created in Scribd.com for our internal use and then the sources were opened for
|
244
|
+
other people to use. All the code in this package has been developed by Alexey Kovyrin for Scribd.com
|
245
|
+
and is released under the MIT license. For more details, see the LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = 'db-charmer'
|
5
|
+
gemspec.summary = 'ActiveRecord Connections Magic'
|
6
|
+
gemspec.description = 'ActiveRecord Connections Magic (slaves, multiple connections, etc)'
|
7
|
+
gemspec.email = 'alexey@kovyrin.net'
|
8
|
+
gemspec.homepage = 'http://github.com/kovyrin/db-charmer'
|
9
|
+
gemspec.authors = ['Alexey Kovyrin']
|
10
|
+
|
11
|
+
gemspec.add_dependency('rails', '>= 2.2.0')
|
12
|
+
end
|
13
|
+
Jeweler::GemcutterTasks.new
|
14
|
+
rescue LoadError
|
15
|
+
puts 'Jeweler not available. Install it with: sudo gem install jeweler'
|
16
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.1
|
data/db-charmer.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{db-charmer}
|
8
|
+
s.version = "1.3.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Alexey Kovyrin"]
|
12
|
+
s.date = %q{2009-10-09}
|
13
|
+
s.description = %q{ActiveRecord Connections Magic (slaves, multiple connections, etc)}
|
14
|
+
s.email = %q{alexey@kovyrin.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"Makefile",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"db-charmer.gemspec",
|
27
|
+
"init.rb",
|
28
|
+
"lib/db_charmer.rb",
|
29
|
+
"lib/db_charmer/active_record_extensions.rb",
|
30
|
+
"lib/db_charmer/association_proxy.rb",
|
31
|
+
"lib/db_charmer/connection_factory.rb",
|
32
|
+
"lib/db_charmer/connection_proxy.rb",
|
33
|
+
"lib/db_charmer/connection_switch.rb",
|
34
|
+
"lib/db_charmer/db_magic.rb",
|
35
|
+
"lib/db_charmer/finder_overrides.rb",
|
36
|
+
"lib/db_charmer/multi_db_migrations.rb",
|
37
|
+
"lib/db_charmer/multi_db_proxy.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/kovyrin/db-charmer}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.5}
|
43
|
+
s.summary = %q{ActiveRecord Connections Magic}
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<rails>, [">= 2.2.0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<rails>, [">= 2.2.0"])
|
53
|
+
end
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rails>, [">= 2.2.0"])
|
56
|
+
end
|
57
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'db_charmer'
|
data/lib/db_charmer.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
puts "Loading DbCharmer..."
|
2
|
+
|
3
|
+
module DbCharmer
|
4
|
+
@@migration_connections_should_exist = Rails.env.production?
|
5
|
+
mattr_accessor :migration_connections_should_exist
|
6
|
+
|
7
|
+
def self.migration_connections_should_exist?
|
8
|
+
!! migration_connections_should_exist
|
9
|
+
end
|
10
|
+
|
11
|
+
@@connections_should_exist = Rails.env.production?
|
12
|
+
mattr_accessor :connections_should_exist
|
13
|
+
|
14
|
+
def self.connections_should_exist?
|
15
|
+
!! connections_should_exist
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.logger
|
19
|
+
return Rails.logger if defined?(Rails)
|
20
|
+
@logger ||= Logger.new(STDERR)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Extending AR..."
|
25
|
+
|
26
|
+
require 'db_charmer/active_record_extensions'
|
27
|
+
require 'db_charmer/connection_factory'
|
28
|
+
require 'db_charmer/connection_proxy'
|
29
|
+
require 'db_charmer/connection_switch'
|
30
|
+
require 'db_charmer/association_proxy'
|
31
|
+
require 'db_charmer/multi_db_proxy'
|
32
|
+
|
33
|
+
# Enable misc AR extensions
|
34
|
+
ActiveRecord::Base.extend(DbCharmer::ActiveRecordExtensions::ClassMethods)
|
35
|
+
|
36
|
+
# Enable connections switching in AR
|
37
|
+
ActiveRecord::Base.extend(DbCharmer::ConnectionSwitch::ClassMethods)
|
38
|
+
|
39
|
+
# Enable connection proxy in AR
|
40
|
+
ActiveRecord::Base.extend(DbCharmer::MultiDbProxy::ClassMethods)
|
41
|
+
ActiveRecord::Base.extend(DbCharmer::MultiDbProxy::MasterSlaveClassMethods)
|
42
|
+
#ActiveRecord::Base.send(:include, DbCharmer::MultiDbProxy::InstanceMethods)
|
43
|
+
|
44
|
+
# Enable connection proxy for associations
|
45
|
+
ActiveRecord::Associations::AssociationProxy.send(:include, DbCharmer::AssociationProxy::InstanceMethods)
|
46
|
+
|
47
|
+
puts "Doing the magic..."
|
48
|
+
|
49
|
+
require 'db_charmer/db_magic'
|
50
|
+
require 'db_charmer/finder_overrides'
|
51
|
+
require 'db_charmer/multi_db_migrations'
|
52
|
+
require 'db_charmer/multi_db_proxy'
|
53
|
+
|
54
|
+
# Enable multi-db migrations
|
55
|
+
ActiveRecord::Migration.extend(DbCharmer::MultiDbMigrations)
|
56
|
+
|
57
|
+
# Enable the magic
|
58
|
+
ActiveRecord::Base.extend(DbCharmer::DbMagic::ClassMethods)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecordExtensions
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def establish_real_connection_if_exists(name, should_exist = false)
|
6
|
+
config = configurations[RAILS_ENV][name.to_s]
|
7
|
+
if should_exist && !config
|
8
|
+
raise ArgumentError, "Invalid connection name (does not exist in database.yml): #{RAILS_ENV}/#{name}"
|
9
|
+
end
|
10
|
+
establish_connection(config) if config
|
11
|
+
end
|
12
|
+
|
13
|
+
#-----------------------------------------------------------------------------
|
14
|
+
@@db_charmer_opts = {}
|
15
|
+
def db_charmer_opts=(opts)
|
16
|
+
@@db_charmer_opts[self.name] = opts
|
17
|
+
end
|
18
|
+
|
19
|
+
def db_charmer_opts
|
20
|
+
@@db_charmer_opts[self.name] || {}
|
21
|
+
end
|
22
|
+
|
23
|
+
#-----------------------------------------------------------------------------
|
24
|
+
@@db_charmer_connection_proxies = {}
|
25
|
+
def db_charmer_connection_proxy=(proxy)
|
26
|
+
@@db_charmer_connection_proxies[self.name] = proxy
|
27
|
+
end
|
28
|
+
|
29
|
+
def db_charmer_connection_proxy
|
30
|
+
@@db_charmer_connection_proxies[self.name]
|
31
|
+
end
|
32
|
+
|
33
|
+
#-----------------------------------------------------------------------------
|
34
|
+
@@db_charmer_slaves = {}
|
35
|
+
def db_charmer_slaves=(slaves)
|
36
|
+
@@db_charmer_slaves[self.name] = slaves
|
37
|
+
end
|
38
|
+
|
39
|
+
def db_charmer_slaves
|
40
|
+
@@db_charmer_slaves[self.name] || []
|
41
|
+
end
|
42
|
+
|
43
|
+
def db_charmer_random_slave
|
44
|
+
return nil unless db_charmer_slaves.any?
|
45
|
+
db_charmer_slaves[rand(db_charmer_slaves.size)]
|
46
|
+
end
|
47
|
+
|
48
|
+
#-----------------------------------------------------------------------------
|
49
|
+
@@db_charmer_connection_levels = Hash.new(0)
|
50
|
+
def db_charmer_connection_level=(level)
|
51
|
+
@@db_charmer_connection_levels[self.name] = level
|
52
|
+
end
|
53
|
+
|
54
|
+
def db_charmer_connection_level
|
55
|
+
@@db_charmer_connection_levels[self.name] || 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def db_charmer_top_level_connection?
|
59
|
+
db_charmer_connection_level.zero?
|
60
|
+
end
|
61
|
+
|
62
|
+
#-----------------------------------------------------------------------------
|
63
|
+
def hijack_connection!
|
64
|
+
return if self.respond_to?(:connection_with_magic)
|
65
|
+
class << self
|
66
|
+
def connection_with_magic
|
67
|
+
db_charmer_connection_proxy || connection_without_magic
|
68
|
+
end
|
69
|
+
alias_method_chain :connection, :magic
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module AssociationProxy
|
3
|
+
module InstanceMethods
|
4
|
+
def on_db(con, &block)
|
5
|
+
@reflection.klass.on_db(con, self, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def on_slave(con = nil, &block)
|
9
|
+
@reflection.klass.on_slave(con, self, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_master(&block)
|
13
|
+
@reflection.klass.on_master(self, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
class ConnectionFactory
|
3
|
+
@@connection_classes = {}
|
4
|
+
|
5
|
+
def self.reset!
|
6
|
+
@@connection_classes = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.connect(db_name, should_exist = false)
|
10
|
+
@@connection_classes[db_name.to_s] ||= establish_connection(db_name.to_s, should_exist)
|
11
|
+
# DbCharmer.logger.warn("ConnectionFactory.connect(#{db_name}) = #{@@connection_classes[db_name.to_s]}")
|
12
|
+
# return @@connection_classes[db_name.to_s]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.establish_connection(db_name, should_exist = false)
|
16
|
+
# DbCharmer.logger.debug("Creating a connection proxy for #{db_name}")
|
17
|
+
abstract_class = generate_abstract_class(db_name, should_exist)
|
18
|
+
DbCharmer::ConnectionProxy.new(abstract_class)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.generate_abstract_class(db_name, should_exist = false)
|
22
|
+
# DbCharmer.logger.info("Generating abstract connection class for #{db_name}: #{abstract_connection_class_name db_name}")
|
23
|
+
|
24
|
+
module_eval <<-EOF, __FILE__, __LINE__ + 1
|
25
|
+
class #{abstract_connection_class_name db_name} < ActiveRecord::Base
|
26
|
+
self.abstract_class = true
|
27
|
+
establish_real_connection_if_exists(:#{db_name}, #{!!should_exist})
|
28
|
+
end
|
29
|
+
EOF
|
30
|
+
|
31
|
+
abstract_connection_class_name(db_name).constantize
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.abstract_connection_class_name(db_name)
|
35
|
+
"::AutoGeneratedAbstractConnectionClass#{db_name.camelize}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
class ConnectionProxy < BlankSlate
|
3
|
+
def initialize(abstract_class)
|
4
|
+
@abstract_connection_class = abstract_class
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(meth, *args, &block)
|
8
|
+
@abstract_connection_class.retrieve_connection.send(meth, *args, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ConnectionSwitch
|
3
|
+
module ClassMethods
|
4
|
+
def coerce_to_connection_proxy(conn, should_exist = true)
|
5
|
+
return nil if conn.nil?
|
6
|
+
|
7
|
+
if conn.kind_of?(Symbol) || conn.kind_of?(String)
|
8
|
+
return DbCharmer::ConnectionFactory.connect(conn, should_exist)
|
9
|
+
end
|
10
|
+
|
11
|
+
if conn.respond_to?(:db_charmer_connection_proxy)
|
12
|
+
return conn.db_charmer_connection_proxy
|
13
|
+
end
|
14
|
+
|
15
|
+
if conn.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
|
16
|
+
return conn
|
17
|
+
end
|
18
|
+
|
19
|
+
raise "Unsupported connection type: #{conn.class}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def switch_connection_to(conn, require_config_to_exist = true)
|
23
|
+
# puts "DEBUG: Assigning connection proxy for #{self}"
|
24
|
+
self.db_charmer_connection_proxy = coerce_to_connection_proxy(conn, require_config_to_exist)
|
25
|
+
self.hijack_connection!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module DbMagic
|
3
|
+
module ClassMethods
|
4
|
+
def db_magic(opt = {})
|
5
|
+
# Make sure we could use our connections management here
|
6
|
+
hijack_connection!
|
7
|
+
|
8
|
+
# Should requested connections exist in the config?
|
9
|
+
should_exist = opt[:should_exist] || DbCharmer.connections_should_exist?
|
10
|
+
|
11
|
+
# Main connection management
|
12
|
+
db_magic_connection(opt[:connection], should_exist) if opt[:connection]
|
13
|
+
|
14
|
+
# Set up slaves pool
|
15
|
+
opt[:slaves] ||= []
|
16
|
+
opt[:slaves] << opt[:slave] if opt[:slave]
|
17
|
+
db_magic_slaves(opt[:slaves], should_exist) if opt[:slaves].any?
|
18
|
+
|
19
|
+
# Setup inheritance magic
|
20
|
+
setup_children_magic(opt)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def setup_children_magic(opt)
|
26
|
+
self.db_charmer_opts = opt.clone
|
27
|
+
|
28
|
+
def self.inherited(child)
|
29
|
+
child.db_magic(self.db_charmer_opts)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def db_magic_connection(conn, should_exist = false)
|
35
|
+
switch_connection_to(conn, should_exist)
|
36
|
+
end
|
37
|
+
|
38
|
+
def db_magic_slaves(slaves, should_exist = false)
|
39
|
+
self.db_charmer_slaves = slaves.collect do |slave|
|
40
|
+
coerce_to_connection_proxy(slave, should_exist)
|
41
|
+
end
|
42
|
+
|
43
|
+
self.extend(DbCharmer::FinderOverrides::ClassMethods)
|
44
|
+
self.send(:include, DbCharmer::FinderOverrides::InstanceMethods)
|
45
|
+
self.extend(DbCharmer::MultiDbProxy::MasterSlaveClassMethods)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module FinderOverrides
|
3
|
+
module ClassMethods
|
4
|
+
SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ]
|
5
|
+
MASTER_METHODS = [ :update, :create, :delete, :destroy, :delete_all, :destroy_all, :update_all, :update_counters ]
|
6
|
+
|
7
|
+
SLAVE_METHODS.each do |slave_method|
|
8
|
+
class_eval <<-EOF
|
9
|
+
def #{slave_method}(*args, &block)
|
10
|
+
first_level_on_slave do
|
11
|
+
super(*args, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
EOF
|
15
|
+
end
|
16
|
+
|
17
|
+
MASTER_METHODS.each do |master_method|
|
18
|
+
class_eval <<-EOF
|
19
|
+
def #{master_method}(*args, &block)
|
20
|
+
on_master do
|
21
|
+
super(*args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
EOF
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def first_level_on_slave
|
30
|
+
if db_charmer_top_level_connection? && on_master.connection.open_transactions.zero?
|
31
|
+
on_slave { yield }
|
32
|
+
else
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
def reload(*args, &block)
|
41
|
+
self.class.on_master do
|
42
|
+
super(*args, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module MultiDbMigrations
|
3
|
+
@@multi_db_name = nil
|
4
|
+
|
5
|
+
def migrate_with_db_wrapper(direction)
|
6
|
+
on_db(@@multi_db_name) { migrate_without_db_wrapper(direction) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_db(db_name)
|
10
|
+
announce "Switching connection to #{db_name}"
|
11
|
+
old_proxy = ActiveRecord::Base.db_charmer_connection_proxy
|
12
|
+
ActiveRecord::Base.switch_connection_to(db_name, DbCharmer.migration_connections_should_exist?)
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
announce "Checking all database connections"
|
16
|
+
ActiveRecord::Base.verify_active_connections!
|
17
|
+
announce "Switching connection back to default"
|
18
|
+
ActiveRecord::Base.switch_connection_to(old_proxy)
|
19
|
+
end
|
20
|
+
|
21
|
+
def db_magic(opts = {})
|
22
|
+
raise ArgumentError, "No connection name - no magic!" unless opts[:connection]
|
23
|
+
@@multi_db_name = opts[:connection]
|
24
|
+
class << self
|
25
|
+
alias_method_chain :migrate, :db_wrapper
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module MultiDbProxy
|
3
|
+
class OnDbProxy < BlankSlate
|
4
|
+
def initialize(model_class, slave)
|
5
|
+
@model = model_class
|
6
|
+
@slave = slave
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def method_missing(meth, *args, &block)
|
12
|
+
@model.on_db(@slave) do |m|
|
13
|
+
m.__send__(meth, *args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def on_db(con, proxy_target = nil)
|
20
|
+
proxy_target ||= self
|
21
|
+
|
22
|
+
# Chain call
|
23
|
+
return OnDbProxy.new(proxy_target, con) unless block_given?
|
24
|
+
|
25
|
+
# Block call
|
26
|
+
begin
|
27
|
+
self.db_charmer_connection_level += 1
|
28
|
+
old_proxy = db_charmer_connection_proxy
|
29
|
+
switch_connection_to(con, DbCharmer.migration_connections_should_exist?)
|
30
|
+
yield(proxy_target)
|
31
|
+
ensure
|
32
|
+
switch_connection_to(old_proxy)
|
33
|
+
self.db_charmer_connection_level -= 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module MasterSlaveClassMethods
|
39
|
+
def on_slave(con = nil, proxy_target = nil, &block)
|
40
|
+
con ||= db_charmer_random_slave
|
41
|
+
raise ArgumentError, "No slaves found in the class and no slave connection given" unless con
|
42
|
+
on_db(con, proxy_target, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_master(proxy_target = nil, &block)
|
46
|
+
on_db(nil, proxy_target, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: db-charmer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexey Kovyrin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-09 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.2.0
|
24
|
+
version:
|
25
|
+
description: ActiveRecord Connections Magic (slaves, multiple connections, etc)
|
26
|
+
email: alexey@kovyrin.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- LICENSE
|
37
|
+
- Makefile
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- db-charmer.gemspec
|
42
|
+
- init.rb
|
43
|
+
- lib/db_charmer.rb
|
44
|
+
- lib/db_charmer/active_record_extensions.rb
|
45
|
+
- lib/db_charmer/association_proxy.rb
|
46
|
+
- lib/db_charmer/connection_factory.rb
|
47
|
+
- lib/db_charmer/connection_proxy.rb
|
48
|
+
- lib/db_charmer/connection_switch.rb
|
49
|
+
- lib/db_charmer/db_magic.rb
|
50
|
+
- lib/db_charmer/finder_overrides.rb
|
51
|
+
- lib/db_charmer/multi_db_migrations.rb
|
52
|
+
- lib/db_charmer/multi_db_proxy.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/kovyrin/db-charmer
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options:
|
59
|
+
- --charset=UTF-8
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: ActiveRecord Connections Magic
|
81
|
+
test_files: []
|
82
|
+
|