db-charmer 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+