db-charmer 1.3.1
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.
- 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
|
+
|