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.
@@ -0,0 +1,2 @@
1
+ /doc
2
+ /pkg
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.
@@ -0,0 +1,2 @@
1
+ doc/files/README_rdoc.html: README.rdoc
2
+ rdoc README.rdoc
@@ -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.
@@ -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
@@ -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'
@@ -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
+