db-charmer 1.7.0.pre6 → 1.7.0.pre7
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/CHANGES +2 -0
- data/README.rdoc +17 -7
- data/db-charmer.gemspec +7 -8
- data/lib/db_charmer.rb +44 -8
- data/lib/db_charmer/action_controller/force_slave_reads.rb +9 -5
- data/lib/db_charmer/active_record/connection_switching.rb +6 -2
- data/lib/db_charmer/active_record/db_magic.rb +10 -5
- data/lib/db_charmer/active_record/multi_db_proxy.rb +9 -0
- data/lib/db_charmer/connection_proxy.rb +3 -0
- data/lib/db_charmer/{abstract_adapter → rails2/abstract_adapter}/log_formatting.rb +1 -0
- data/lib/db_charmer/{active_record/finder_overrides.rb → rails2/active_record/master_slave_routing.rb} +1 -13
- data/lib/db_charmer/{active_record → rails2/active_record}/named_scope/scope_proxy.rb +0 -0
- data/lib/db_charmer/rails3/abstract_adapter/connection_name.rb +38 -0
- data/lib/db_charmer/rails3/active_record/log_subscriber.rb +23 -0
- data/lib/db_charmer/rails3/active_record/master_slave_routing.rb +46 -0
- data/lib/db_charmer/rails3/active_record/relation/connection_routing.rb +147 -0
- data/lib/db_charmer/rails3/active_record/relation_method.rb +28 -0
- data/lib/db_charmer/sharding/stub_connection.rb +8 -0
- data/lib/db_charmer/version.rb +1 -1
- metadata +41 -38
data/CHANGES
CHANGED
data/README.rdoc
CHANGED
@@ -560,26 +560,36 @@ return an array of sharding connection names or connection configurations to be
|
|
560
560
|
establish shard connections in a loop.
|
561
561
|
|
562
562
|
|
563
|
-
== Documentation
|
563
|
+
== Documentation/Questions
|
564
564
|
|
565
565
|
For more information on the plugin internals, please check out the source code. All the plugin's
|
566
|
-
code is ~100% covered with
|
566
|
+
code is ~100% covered with tests that were placed in a separate staging rails project located
|
567
567
|
at http://github.com/kovyrin/db-charmer-sandbox. The project has unit tests for all or at least the
|
568
568
|
most of the parts of plugin's code.
|
569
569
|
|
570
|
+
If you have any questions regarding this project, you could contact the author using
|
571
|
+
the DbCharmer Users Group mailing list:
|
572
|
+
|
573
|
+
- Group Info: http://groups.google.com/group/db-charmer
|
574
|
+
- Subscribe using the info page or by sending an email to db-charmer-subscribe@googlegroups.com
|
575
|
+
|
570
576
|
|
571
577
|
== What Ruby and Rails implementations does it work for?
|
572
578
|
|
573
|
-
We have a continuous integration setups for this plugin on MRI 1.8.7 with Rails 2.2
|
574
|
-
We use the plugin in production on Scribd.com with MRI (
|
575
|
-
Sinatra and plain Rack applications.
|
579
|
+
We have a continuous integration setups for this plugin on MRI 1.8.7 with Rails 2.2, 2.3 and 3.0.
|
580
|
+
We use the plugin in production on Scribd.com with MRI (Ruby Enterprise Edition) 1.8.7 and
|
581
|
+
Rails 2.2, Rails 2.3, Sinatra and plain Rack applications.
|
582
|
+
|
583
|
+
Starting with version 1.7.0 we support Rails 3.0, but until further notice we consider it a
|
584
|
+
beta-quality feature since we do not run any production software on this version of Rails. If you
|
585
|
+
run your application on Rails 3.0, please contact the author.
|
576
586
|
|
577
587
|
|
578
588
|
== Who are the authors?
|
579
589
|
|
580
590
|
This plugin has been created in Scribd.com for our internal use and then the sources were opened for
|
581
|
-
other people to use. Most of the code in this package has been developed by
|
582
|
-
and is released under the MIT license. For more details, see the LICENSE file.
|
591
|
+
other people to use. Most of the code in this package has been developed by Oleksiy Kovyrin for
|
592
|
+
Scribd.com and is released under the MIT license. For more details, see the LICENSE file.
|
583
593
|
|
584
594
|
Other contributors who have helped with the development of this library are (alphabetically ordered):
|
585
595
|
* Allen Madsen
|
data/db-charmer.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = DbCharmer::Version::STRING
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
|
10
|
-
s.authors = [ '
|
10
|
+
s.authors = [ 'Oleksiy Kovyrin' ]
|
11
11
|
s.email = 'alexey@kovyrin.net'
|
12
12
|
s.homepage = 'http://github.com/kovyrin/db-charmer'
|
13
13
|
s.summary = 'ActiveRecord Connections Magic (slaves, multiple connections, etc)'
|
@@ -15,16 +15,15 @@ Gem::Specification.new do |s|
|
|
15
15
|
|
16
16
|
s.rdoc_options = [ '--charset=UTF-8' ]
|
17
17
|
|
18
|
-
s.add_development_dependency 'rspec'
|
19
|
-
s.add_development_dependency 'yard'
|
20
|
-
s.add_development_dependency 'actionpack'
|
21
|
-
|
22
18
|
s.files = `git ls-files`.split("\n")
|
23
19
|
s.require_paths = [ 'lib' ]
|
24
20
|
s.extra_rdoc_files = [ 'LICENSE', 'README.rdoc' ]
|
25
21
|
|
26
22
|
# Dependencies
|
27
|
-
s.add_dependency 'activesupport', '
|
28
|
-
s.add_dependency 'activerecord', '
|
29
|
-
end
|
23
|
+
s.add_dependency 'activesupport', '< 3.1'
|
24
|
+
s.add_dependency 'activerecord', '< 3.1'
|
30
25
|
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
s.add_development_dependency 'yard'
|
28
|
+
s.add_development_dependency 'actionpack'
|
29
|
+
end
|
data/lib/db_charmer.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# In Rails 2.2 they did not add it to the autoload so it won't work w/o this require
|
2
|
+
require 'active_record/version' unless defined?(::ActiveRecord::VERSION::MAJOR)
|
3
|
+
|
1
4
|
module DbCharmer
|
2
5
|
# Configure autoload
|
3
6
|
autoload :Sharding, 'db_charmer/sharding'
|
@@ -6,6 +9,16 @@ module DbCharmer
|
|
6
9
|
autoload :ForceSlaveReads, 'db_charmer/action_controller/force_slave_reads'
|
7
10
|
end
|
8
11
|
|
12
|
+
# Used in all Rails3-specific places
|
13
|
+
def self.rails3?
|
14
|
+
::ActiveRecord::VERSION::MAJOR > 2
|
15
|
+
end
|
16
|
+
|
17
|
+
# Used in all Rails2-specific places
|
18
|
+
def self.rails2?
|
19
|
+
::ActiveRecord::VERSION::MAJOR == 2
|
20
|
+
end
|
21
|
+
|
9
22
|
# Accessors
|
10
23
|
@@connections_should_exist = true
|
11
24
|
mattr_accessor :connections_should_exist
|
@@ -62,7 +75,8 @@ private
|
|
62
75
|
old_hijack_new_classes = @@hijack_new_classes
|
63
76
|
begin
|
64
77
|
@@hijack_new_classes = true
|
65
|
-
|
78
|
+
subclasses_method = DbCharmer.rails3? ? :descendants : :subclasses
|
79
|
+
::ActiveRecord::Base.send(subclasses_method).each do |subclass|
|
66
80
|
subclass.hijack_connection!
|
67
81
|
end
|
68
82
|
yield
|
@@ -87,9 +101,16 @@ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ClassAttributes)
|
|
87
101
|
require 'db_charmer/active_record/connection_switching'
|
88
102
|
ActiveRecord::Base.extend(DbCharmer::ActiveRecord::ConnectionSwitching)
|
89
103
|
|
90
|
-
# Enable
|
91
|
-
|
92
|
-
|
104
|
+
# Enable AR logging extensions
|
105
|
+
if DbCharmer.rails3?
|
106
|
+
require 'db_charmer/rails3/abstract_adapter/connection_name'
|
107
|
+
require 'db_charmer/rails3/active_record/log_subscriber'
|
108
|
+
ActiveRecord::LogSubscriber.send(:include, DbCharmer::ActiveRecord::LogSubscriber)
|
109
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::ConnectionName)
|
110
|
+
else
|
111
|
+
require 'db_charmer/rails2/abstract_adapter/log_formatting'
|
112
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, DbCharmer::AbstractAdapter::LogFormatting)
|
113
|
+
end
|
93
114
|
|
94
115
|
# Enable connection proxy in AR
|
95
116
|
require 'db_charmer/active_record/multi_db_proxy'
|
@@ -97,9 +118,19 @@ ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::ClassMethods)
|
|
97
118
|
ActiveRecord::Base.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
|
98
119
|
ActiveRecord::Base.send(:include, DbCharmer::ActiveRecord::MultiDbProxy::InstanceMethods)
|
99
120
|
|
100
|
-
# Enable connection proxy for
|
101
|
-
|
102
|
-
|
121
|
+
# Enable connection proxy for relations
|
122
|
+
if DbCharmer.rails3?
|
123
|
+
require 'db_charmer/rails3/active_record/relation_method'
|
124
|
+
require 'db_charmer/rails3/active_record/relation/connection_routing'
|
125
|
+
ActiveRecord::Base.extend(DbCharmer::ActiveRecord::RelationMethod)
|
126
|
+
ActiveRecord::Relation.send(:include, DbCharmer::ActiveRecord::Relation::ConnectionRouting)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Enable connection proxy for scopes (rails 2.x only)
|
130
|
+
if DbCharmer.rails2?
|
131
|
+
require 'db_charmer/rails2/active_record/named_scope/scope_proxy'
|
132
|
+
ActiveRecord::NamedScope::Scope.send(:include, DbCharmer::ActiveRecord::NamedScope::ScopeProxy)
|
133
|
+
end
|
103
134
|
|
104
135
|
# Enable connection proxy for associations
|
105
136
|
# WARNING: Inject methods to association class right here (they proxy include calls somewhere else, so include does not work)
|
@@ -131,7 +162,12 @@ require 'db_charmer/active_record/migration/multi_db_migrations'
|
|
131
162
|
ActiveRecord::Migration.extend(DbCharmer::ActiveRecord::Migration::MultiDbMigrations)
|
132
163
|
|
133
164
|
# Enable the magic
|
134
|
-
|
165
|
+
if DbCharmer.rails3?
|
166
|
+
require 'db_charmer/rails3/active_record/master_slave_routing'
|
167
|
+
else
|
168
|
+
require 'db_charmer/rails2/active_record/master_slave_routing'
|
169
|
+
end
|
170
|
+
|
135
171
|
require 'db_charmer/active_record/sharding'
|
136
172
|
require 'db_charmer/active_record/db_magic'
|
137
173
|
ActiveRecord::Base.extend(DbCharmer::ActiveRecord::DbMagic)
|
@@ -35,8 +35,10 @@ module DbCharmer
|
|
35
35
|
end
|
36
36
|
|
37
37
|
module InstanceMethods
|
38
|
+
DISPATCH_METHOD = (DbCharmer.rails3?) ? :process_action : :perform_action
|
39
|
+
|
38
40
|
def self.included(base)
|
39
|
-
base.alias_method_chain
|
41
|
+
base.alias_method_chain DISPATCH_METHOD, :forced_slave_reads
|
40
42
|
end
|
41
43
|
|
42
44
|
def force_slave_reads!
|
@@ -53,11 +55,13 @@ module DbCharmer
|
|
53
55
|
|
54
56
|
protected
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
class_eval <<-EOF
|
59
|
+
def #{DISPATCH_METHOD}_with_forced_slave_reads(*args, &block)
|
60
|
+
DbCharmer.with_controller(self) do
|
61
|
+
#{DISPATCH_METHOD}_without_forced_slave_reads(*args, &block)
|
62
|
+
end
|
59
63
|
end
|
60
|
-
|
64
|
+
EOF
|
61
65
|
end
|
62
66
|
|
63
67
|
end
|
@@ -29,6 +29,7 @@ module DbCharmer
|
|
29
29
|
def hijack_connection!
|
30
30
|
return if self.respond_to?(:connection_with_magic)
|
31
31
|
class << self
|
32
|
+
# Make sure we check our accessors before going to the default connection retrieval method
|
32
33
|
def connection_with_magic
|
33
34
|
db_charmer_remapped_connection || db_charmer_connection_proxy || connection_without_magic
|
34
35
|
end
|
@@ -62,8 +63,8 @@ module DbCharmer
|
|
62
63
|
end
|
63
64
|
|
64
65
|
#-----------------------------------------------------------------------------------------------------------------
|
65
|
-
def switch_connection_to(conn,
|
66
|
-
new_conn = coerce_to_connection_proxy(conn,
|
66
|
+
def switch_connection_to(conn, should_exist = true)
|
67
|
+
new_conn = coerce_to_connection_proxy(conn, should_exist)
|
67
68
|
|
68
69
|
if db_charmer_connection_proxy.is_a?(DbCharmer::Sharding::StubConnection)
|
69
70
|
db_charmer_connection_proxy.set_real_connection(new_conn)
|
@@ -71,7 +72,10 @@ module DbCharmer
|
|
71
72
|
|
72
73
|
self.db_charmer_connection_proxy = new_conn
|
73
74
|
self.hijack_connection!
|
75
|
+
|
76
|
+
# self.reset_column_information
|
74
77
|
end
|
78
|
+
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
@@ -21,7 +21,7 @@ module DbCharmer
|
|
21
21
|
forced_slave_reads = opt.has_key?(:force_slave_reads) ? opt[:force_slave_reads] : true
|
22
22
|
|
23
23
|
# Setup all the slaves related magic if needed
|
24
|
-
setup_slaves_magic(opt[:slaves], forced_slave_reads, should_exist)
|
24
|
+
setup_slaves_magic(opt[:slaves], forced_slave_reads, should_exist)
|
25
25
|
|
26
26
|
# Setup inheritance magic
|
27
27
|
setup_children_magic(opt)
|
@@ -63,15 +63,20 @@ module DbCharmer
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def setup_slaves_magic(slaves, force_slave_reads, should_exist = true)
|
66
|
+
self.db_charmer_force_slave_reads = force_slave_reads
|
67
|
+
|
68
|
+
# Initialize the slave connections list
|
66
69
|
self.db_charmer_slaves = slaves.collect do |slave|
|
67
70
|
coerce_to_connection_proxy(slave, should_exist)
|
68
71
|
end
|
72
|
+
return if db_charmer_slaves.empty?
|
69
73
|
|
70
|
-
|
71
|
-
|
72
|
-
self.extend(DbCharmer::ActiveRecord::FinderOverrides::ClassMethods)
|
73
|
-
self.send(:include, DbCharmer::ActiveRecord::FinderOverrides::InstanceMethods)
|
74
|
+
# Enable on_slave/on_master methods
|
74
75
|
self.extend(DbCharmer::ActiveRecord::MultiDbProxy::MasterSlaveClassMethods)
|
76
|
+
|
77
|
+
# Enable automatic master/slave queries routing (we have specialized versions on those modules for rails2/3)
|
78
|
+
self.extend(DbCharmer::ActiveRecord::MasterSlaveRouting::ClassMethods)
|
79
|
+
self.send(:include, DbCharmer::ActiveRecord::MasterSlaveRouting::InstanceMethods)
|
75
80
|
end
|
76
81
|
|
77
82
|
end
|
@@ -62,6 +62,15 @@ module DbCharmer
|
|
62
62
|
def on_master(proxy_target = nil, &block)
|
63
63
|
on_db(db_charmer_default_connection, proxy_target, &block)
|
64
64
|
end
|
65
|
+
|
66
|
+
def first_level_on_slave
|
67
|
+
first_level = db_charmer_top_level_connection? && on_master.connection.open_transactions.zero?
|
68
|
+
if first_level && db_charmer_force_slave_reads? && db_charmer_slaves.any?
|
69
|
+
on_slave { yield }
|
70
|
+
else
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
end
|
65
74
|
end
|
66
75
|
end
|
67
76
|
end
|
@@ -4,6 +4,9 @@ module DbCharmer
|
|
4
4
|
# We need to do this because in Rails 2.3 BasicObject does not remove object_id method, which is stupid
|
5
5
|
undef_method(:object_id) if instance_methods.member?('object_id')
|
6
6
|
|
7
|
+
# We use this to get a connection class from the proxy
|
8
|
+
attr_accessor :abstract_connection_class
|
9
|
+
|
7
10
|
def initialize(abstract_class, db_name)
|
8
11
|
@abstract_connection_class = abstract_class
|
9
12
|
@db_name = db_name
|
@@ -11,6 +11,7 @@ module DbCharmer
|
|
11
11
|
@config[:connection_name]
|
12
12
|
end
|
13
13
|
|
14
|
+
# Rails 2.X specific logging method
|
14
15
|
def format_log_entry_with_connection_name(message, dump = nil)
|
15
16
|
msg = connection_name ? "[#{connection_name}] " : ''
|
16
17
|
msg = " \e[0;34;1m#{msg}\e[0m" if connection_name && ::ActiveRecord::Base.colorize_logging
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module DbCharmer
|
2
2
|
module ActiveRecord
|
3
|
-
module
|
3
|
+
module MasterSlaveRouting
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
SLAVE_METHODS = [ :find_by_sql, :count_by_sql, :calculate ]
|
@@ -34,18 +34,6 @@ module DbCharmer
|
|
34
34
|
super(*args, &block)
|
35
35
|
end
|
36
36
|
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def first_level_on_slave
|
41
|
-
first_level = db_charmer_top_level_connection? && on_master.connection.open_transactions.zero?
|
42
|
-
if first_level && db_charmer_force_slave_reads?
|
43
|
-
on_slave { yield }
|
44
|
-
else
|
45
|
-
yield
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
37
|
end
|
50
38
|
|
51
39
|
module InstanceMethods
|
File without changes
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module AbstractAdapter
|
3
|
+
module ConnectionName
|
4
|
+
|
5
|
+
# We use this proxy to push connection name down to instrumenters w/o monkey-patching the log method itself
|
6
|
+
class InstrumenterDecorator < ActiveSupport::BasicObject
|
7
|
+
def initialize(adapter, instrumenter)
|
8
|
+
@adapter = adapter
|
9
|
+
@instrumenter = instrumenter
|
10
|
+
end
|
11
|
+
|
12
|
+
def instrument(name, payload = {}, &block)
|
13
|
+
payload[:connection_name] ||= @adapter.connection_name
|
14
|
+
@instrumenter.instrument(name, payload, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(meth, *args, &block)
|
18
|
+
@instrumenter.send(meth, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.alias_method_chain :initialize, :connection_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def connection_name
|
27
|
+
raise "Can't find connection configuration!" unless @config
|
28
|
+
@config[:connection_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize_with_connection_name(*args)
|
32
|
+
initialize_without_connection_name(*args)
|
33
|
+
@instrumenter = InstrumenterDecorator.new(self, @instrumenter)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module LogSubscriber
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:attr_accessor, :connection_name)
|
7
|
+
base.alias_method_chain :sql, :connection_name
|
8
|
+
base.alias_method_chain :debug, :connection_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def sql_with_connection_name(event)
|
12
|
+
self.connection_name = event.payload[:connection_name]
|
13
|
+
sql_without_connection_name(event)
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug_with_connection_name(msg)
|
17
|
+
conn = connection_name ? color(" [#{connection_name}]", ActiveSupport::LogSubscriber::BLUE, true) : ''
|
18
|
+
debug_without_connection_name(conn + msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module MasterSlaveRouting
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
SLAVE_METHODS = [ :find_by_sql, :count_by_sql ]
|
7
|
+
MASTER_METHODS = [ ] # I don't know any methods in AR::Base that change data directly w/o going to the relation object
|
8
|
+
|
9
|
+
SLAVE_METHODS.each do |slave_method|
|
10
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
11
|
+
def #{slave_method}(*args, &block)
|
12
|
+
first_level_on_slave do
|
13
|
+
super(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
MASTER_METHODS.each do |master_method|
|
20
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
21
|
+
def #{master_method}(*args, &block)
|
22
|
+
on_master do
|
23
|
+
super(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module InstanceMethods
|
31
|
+
MASTER_METHODS = [ :reload ]
|
32
|
+
|
33
|
+
MASTER_METHODS.each do |master_method|
|
34
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
35
|
+
def #{master_method}(*args, &block)
|
36
|
+
self.class.on_master do
|
37
|
+
super(*args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module Relation
|
4
|
+
module ConnectionRouting
|
5
|
+
|
6
|
+
# All the methods that could be querying the database
|
7
|
+
SLAVE_METHODS = [ :calculate, :exists? ]
|
8
|
+
MASTER_METHODS = [ :delete, :delete_all, :destroy, :destroy_all, :reload, :update, :update_all ]
|
9
|
+
ALL_METHODS = SLAVE_METHODS + MASTER_METHODS
|
10
|
+
|
11
|
+
DB_CHARMER_ATTRIBUTES = [ :db_charmer_connection, :db_charmer_connection_is_forced, :db_charmer_enable_slaves ]
|
12
|
+
|
13
|
+
# Define the default relation connection + override all the query methods here
|
14
|
+
def self.included(base)
|
15
|
+
init_attributes(base)
|
16
|
+
init_routing(base)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define our attributes + spawn methods shit needs to be changed to make sure our accessors are copied over to the new instances
|
20
|
+
def self.init_attributes(base)
|
21
|
+
DB_CHARMER_ATTRIBUTES.each do |attr|
|
22
|
+
base.send(:attr_accessor, attr)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Override spawn methods
|
26
|
+
base.alias_method_chain :except, :db_charmer
|
27
|
+
base.alias_method_chain :only, :db_charmer
|
28
|
+
end
|
29
|
+
|
30
|
+
# Override all query methods
|
31
|
+
def self.init_routing(base)
|
32
|
+
ALL_METHODS.each do |meth|
|
33
|
+
base.alias_method_chain meth, :db_charmer
|
34
|
+
end
|
35
|
+
|
36
|
+
# Special case: for normal selects we go to the slave, but for selects with a lock we should use master
|
37
|
+
base.alias_method_chain :to_a, :db_charmer
|
38
|
+
end
|
39
|
+
|
40
|
+
# Copy db_charmer attributes in addition to what they're copying
|
41
|
+
def except_with_db_charmer(*args)
|
42
|
+
except_without_db_charmer(*args).tap do |result|
|
43
|
+
copy_db_charmer_options(self, result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Copy db_charmer attributes in addition to what they're copying
|
48
|
+
def only_with_db_charmer(*args)
|
49
|
+
only_without_db_charmer(*args).tap do |result|
|
50
|
+
copy_db_charmer_options(self, result)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Copy our accessors from one instance to another
|
55
|
+
def copy_db_charmer_options(src, dst)
|
56
|
+
DB_CHARMER_ATTRIBUTES.each do |attr|
|
57
|
+
dst.send("#{attr}=".to_sym, src.send(attr))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Connection switching (changes the default relation connection)
|
62
|
+
def on_db(con, &block)
|
63
|
+
if block_given?
|
64
|
+
@klass.on_db(con, &block)
|
65
|
+
else
|
66
|
+
clone.tap do |result|
|
67
|
+
result.db_charmer_connection = con
|
68
|
+
result.db_charmer_connection_is_forced = true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Make sure we get the right connection here
|
74
|
+
def connection
|
75
|
+
@klass.on_db(db_charmer_connection).connection
|
76
|
+
end
|
77
|
+
|
78
|
+
# Selects preferred destination (master/slave/default) for a query
|
79
|
+
def select_destination(method, recommendation = :default)
|
80
|
+
# If this relation was created within a forced connection block (e.g Model.on_db(:foo).relation)
|
81
|
+
# Then we should use that connection everywhere except cases when a model is slave-enabled
|
82
|
+
# in those cases DML queries go to the master
|
83
|
+
if db_charmer_connection_is_forced
|
84
|
+
return :master if db_charmer_enable_slaves && MASTER_METHODS.member?(method)
|
85
|
+
return :default
|
86
|
+
end
|
87
|
+
|
88
|
+
# If this relation is created from a slave-enabled model, let's do the routing if possible
|
89
|
+
if db_charmer_enable_slaves
|
90
|
+
return :slave if SLAVE_METHODS.member?(method)
|
91
|
+
return :master if MASTER_METHODS.member?(method)
|
92
|
+
else
|
93
|
+
# Make sure we do not use recommended destination
|
94
|
+
recommendation = :default
|
95
|
+
end
|
96
|
+
|
97
|
+
# If nothing else came up, let's use the default or recommended connection
|
98
|
+
return recommendation
|
99
|
+
end
|
100
|
+
|
101
|
+
# Switch the model to default relation connection
|
102
|
+
def switch_connection_for_method(method, recommendation = nil)
|
103
|
+
# Choose where to send the query
|
104
|
+
destination ||= select_destination(method, recommendation)
|
105
|
+
|
106
|
+
# What method to use
|
107
|
+
on_db_method = [ :on_db, db_charmer_connection ]
|
108
|
+
on_db_method = :on_master if destination == :master
|
109
|
+
on_db_method = :first_level_on_slave if destination == :slave
|
110
|
+
|
111
|
+
# Perform the query
|
112
|
+
@klass.send(*on_db_method) do
|
113
|
+
yield
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# For normal selects we go to the slave, but for selects with a lock we should use master
|
118
|
+
def to_a_with_db_charmer(*args, &block)
|
119
|
+
preferred_destination = :slave
|
120
|
+
preferred_destination = :master if lock_value
|
121
|
+
|
122
|
+
switch_connection_for_method(:to_a, preferred_destination) do
|
123
|
+
to_a_without_db_charmer(*args, &block)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Need this to mimick alias_method_chain name generation (exists? => exists_with_db_charmer?)
|
128
|
+
def self.aliased_method_name(target, with)
|
129
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
130
|
+
"#{aliased_target}_#{with}_db_charmer#{punctuation}"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Override all the query methods here
|
134
|
+
ALL_METHODS.each do |method|
|
135
|
+
class_eval <<-EOF, __FILE__, __LINE__ + 1
|
136
|
+
def #{aliased_method_name method, :with}(*args, &block)
|
137
|
+
switch_connection_for_method(:#{method.to_s}) do
|
138
|
+
#{aliased_method_name method, :without}(*args, &block)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
EOF
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DbCharmer
|
2
|
+
module ActiveRecord
|
3
|
+
module RelationMethod
|
4
|
+
|
5
|
+
def self.extended(base)
|
6
|
+
class << base
|
7
|
+
alias_method_chain :relation, :db_charmer
|
8
|
+
alias_method_chain :arel_engine, :db_charmer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Create a relation object and initialize its default connection
|
13
|
+
def relation_with_db_charmer(*args, &block)
|
14
|
+
relation_without_db_charmer(*args, &block).tap do |rel|
|
15
|
+
rel.db_charmer_connection = self.connection
|
16
|
+
rel.db_charmer_enable_slaves = self.db_charmer_slaves.any?
|
17
|
+
rel.db_charmer_connection_is_forced = !db_charmer_top_level_connection?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use the model itself an engine for Arel, do not fall back to AR::Base
|
22
|
+
def arel_engine_with_db_charmer(*)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -18,6 +18,10 @@ module DbCharmer
|
|
18
18
|
@real_conn = real_conn
|
19
19
|
end
|
20
20
|
|
21
|
+
def db_charmer_connection_name
|
22
|
+
"StubConnection"
|
23
|
+
end
|
24
|
+
|
21
25
|
def real_connection
|
22
26
|
# Return memoized real connection
|
23
27
|
return @real_conn if @real_conn
|
@@ -44,6 +48,10 @@ module DbCharmer
|
|
44
48
|
raise ::ActiveRecord::ConnectionNotEstablished, "No real connection to proxy this method to!"
|
45
49
|
end
|
46
50
|
|
51
|
+
if real_connection.kind_of?(DbCharmer::Sharding::StubConnection)
|
52
|
+
raise ::ActiveRecord::ConnectionNotEstablished, "You have to switch connection on your model before using it!"
|
53
|
+
end
|
54
|
+
|
47
55
|
# Proxy the call to our real connection target
|
48
56
|
real_connection.__send__(meth, *args, &block)
|
49
57
|
end
|
data/lib/db_charmer/version.rb
CHANGED
metadata
CHANGED
@@ -1,55 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db-charmer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 1923832055
|
5
5
|
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 7
|
9
9
|
- 0
|
10
10
|
- pre
|
11
|
-
-
|
12
|
-
version: 1.7.0.
|
11
|
+
- 7
|
12
|
+
version: 1.7.0.pre7
|
13
13
|
platform: ruby
|
14
14
|
authors:
|
15
|
-
-
|
15
|
+
- Oleksiy Kovyrin
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2011-
|
21
|
-
default_executable:
|
20
|
+
date: 2011-08-27 00:00:00 Z
|
22
21
|
dependencies:
|
23
22
|
- !ruby/object:Gem::Dependency
|
24
|
-
name:
|
23
|
+
name: activesupport
|
25
24
|
prerelease: false
|
26
25
|
requirement: &id001 !ruby/object:Gem::Requirement
|
27
26
|
none: false
|
28
27
|
requirements:
|
29
|
-
- -
|
28
|
+
- - <
|
30
29
|
- !ruby/object:Gem::Version
|
31
|
-
hash:
|
30
|
+
hash: 5
|
32
31
|
segments:
|
33
|
-
-
|
34
|
-
|
35
|
-
|
32
|
+
- 3
|
33
|
+
- 1
|
34
|
+
version: "3.1"
|
35
|
+
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
38
|
+
name: activerecord
|
39
39
|
prerelease: false
|
40
40
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
|
-
- -
|
43
|
+
- - <
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
hash:
|
45
|
+
hash: 5
|
46
46
|
segments:
|
47
|
-
-
|
48
|
-
|
49
|
-
|
47
|
+
- 3
|
48
|
+
- 1
|
49
|
+
version: "3.1"
|
50
|
+
type: :runtime
|
50
51
|
version_requirements: *id002
|
51
52
|
- !ruby/object:Gem::Dependency
|
52
|
-
name:
|
53
|
+
name: rspec
|
53
54
|
prerelease: false
|
54
55
|
requirement: &id003 !ruby/object:Gem::Requirement
|
55
56
|
none: false
|
@@ -63,34 +64,32 @@ dependencies:
|
|
63
64
|
type: :development
|
64
65
|
version_requirements: *id003
|
65
66
|
- !ruby/object:Gem::Dependency
|
66
|
-
name:
|
67
|
+
name: yard
|
67
68
|
prerelease: false
|
68
69
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
70
|
none: false
|
70
71
|
requirements:
|
71
|
-
- -
|
72
|
+
- - ">="
|
72
73
|
- !ruby/object:Gem::Version
|
73
|
-
hash:
|
74
|
+
hash: 3
|
74
75
|
segments:
|
75
|
-
-
|
76
|
-
|
77
|
-
|
78
|
-
type: :runtime
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
type: :development
|
79
79
|
version_requirements: *id004
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
|
-
name:
|
81
|
+
name: actionpack
|
82
82
|
prerelease: false
|
83
83
|
requirement: &id005 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
|
-
- -
|
86
|
+
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
hash:
|
88
|
+
hash: 3
|
89
89
|
segments:
|
90
|
-
-
|
91
|
-
|
92
|
-
|
93
|
-
type: :runtime
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
type: :development
|
94
93
|
version_requirements: *id005
|
95
94
|
description: DbCharmer is a Rails plugin (and gem) that could be used to manage AR model connections, implement master/slave query schemes, sharding and other magic features many high-scale applications need.
|
96
95
|
email: alexey@kovyrin.net
|
@@ -111,21 +110,26 @@ files:
|
|
111
110
|
- db-charmer.gemspec
|
112
111
|
- init.rb
|
113
112
|
- lib/db_charmer.rb
|
114
|
-
- lib/db_charmer/abstract_adapter/log_formatting.rb
|
115
113
|
- lib/db_charmer/action_controller/force_slave_reads.rb
|
116
114
|
- lib/db_charmer/active_record/association_preload.rb
|
117
115
|
- lib/db_charmer/active_record/class_attributes.rb
|
118
116
|
- lib/db_charmer/active_record/connection_switching.rb
|
119
117
|
- lib/db_charmer/active_record/db_magic.rb
|
120
|
-
- lib/db_charmer/active_record/finder_overrides.rb
|
121
118
|
- lib/db_charmer/active_record/migration/multi_db_migrations.rb
|
122
119
|
- lib/db_charmer/active_record/multi_db_proxy.rb
|
123
|
-
- lib/db_charmer/active_record/named_scope/scope_proxy.rb
|
124
120
|
- lib/db_charmer/active_record/sharding.rb
|
125
121
|
- lib/db_charmer/connection_factory.rb
|
126
122
|
- lib/db_charmer/connection_proxy.rb
|
127
123
|
- lib/db_charmer/core_extensions.rb
|
128
124
|
- lib/db_charmer/force_slave_reads.rb
|
125
|
+
- lib/db_charmer/rails2/abstract_adapter/log_formatting.rb
|
126
|
+
- lib/db_charmer/rails2/active_record/master_slave_routing.rb
|
127
|
+
- lib/db_charmer/rails2/active_record/named_scope/scope_proxy.rb
|
128
|
+
- lib/db_charmer/rails3/abstract_adapter/connection_name.rb
|
129
|
+
- lib/db_charmer/rails3/active_record/log_subscriber.rb
|
130
|
+
- lib/db_charmer/rails3/active_record/master_slave_routing.rb
|
131
|
+
- lib/db_charmer/rails3/active_record/relation/connection_routing.rb
|
132
|
+
- lib/db_charmer/rails3/active_record/relation_method.rb
|
129
133
|
- lib/db_charmer/sharding.rb
|
130
134
|
- lib/db_charmer/sharding/connection.rb
|
131
135
|
- lib/db_charmer/sharding/method.rb
|
@@ -136,7 +140,6 @@ files:
|
|
136
140
|
- lib/db_charmer/sharding/stub_connection.rb
|
137
141
|
- lib/db_charmer/version.rb
|
138
142
|
- lib/tasks/databases.rake
|
139
|
-
has_rdoc: true
|
140
143
|
homepage: http://github.com/kovyrin/db-charmer
|
141
144
|
licenses: []
|
142
145
|
|
@@ -168,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
171
|
requirements: []
|
169
172
|
|
170
173
|
rubyforge_project:
|
171
|
-
rubygems_version: 1.
|
174
|
+
rubygems_version: 1.8.8
|
172
175
|
signing_key:
|
173
176
|
specification_version: 3
|
174
177
|
summary: ActiveRecord Connections Magic (slaves, multiple connections, etc)
|