schoefmax-multi_db 0.1.1 → 0.1.2

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/README.rdoc CHANGED
@@ -10,13 +10,14 @@ master database.
10
10
 
11
11
  === Caveats
12
12
 
13
- * works with activerecord 2.1 - 2.2
13
+ * works with activerecord 2.1 and 2.2, but is not threadsafe (yet)
14
+ * when using Passenger or lightspeed you will probably need to introduce a before_filter which checks if the proxy is setup (see the discussion in the masochism readme: http://github.com/technoweenie/masochism/tree/master)
14
15
 
15
16
  === Install
16
17
 
17
18
  gem install schoefmax-multi_db --source http://gems.github.com
18
19
 
19
- In Rails 2.1, add this to your environment.rb:
20
+ When using Rails, add this to your environment.rb:
20
21
 
21
22
  config.gem 'schoefmax-multi_db', :lib => 'multi_db', :source => 'http://gems.github.com'
22
23
 
@@ -38,13 +39,18 @@ In your database.yml, add sections for the slaves, e.g.:
38
39
  password:
39
40
  host: 10.0.0.2
40
41
 
41
- production_slave_database2: # another slave
42
+ production_slave_database_2: # another slave
42
43
  ...
43
- production_slave_database_some_server: # yet another one
44
+ production_slave_database_in_india: # yet another one
44
45
  ...
45
46
 
46
- *NOTE*: multi_db identifies slave databases by looking for "slave_database"
47
- somewhere in the database name!
47
+ *NOTE*: multi_db identifies slave databases by looking for entries of the form
48
+ "<tt><environment>_slave_database<_optional_name></tt>". As a (useless) side effect you
49
+ get abstract classes named <tt>MultiDb::SlaveDatabaseInIndia</tt> etc.
50
+ The advantage of specifying the slaves explicitly, instead of the master, is that
51
+ you can use the same configuration file for scripts that don't use multi_db.
52
+ Also, when you decide to disable multi_db for some reason, you don't have to
53
+ swap hosts in your <tt>database.yml</tt> from master to slave (which is easy to forget...).
48
54
 
49
55
  To enable the proxy globally, add this to your environment.rb, or some file in
50
56
  config/initializers:
@@ -58,38 +64,88 @@ the corresponding file in config/environments:
58
64
  MultiDb::ConnectionProxy.setup!
59
65
  end
60
66
 
61
- In the development and test environments, you can use the same configuration
62
- for your master and slave databases.
67
+ In the development and test environments, you can use identical configurations
68
+ for master and slave connections. This can help you finding (some of the) issues
69
+ your application might have with a replicated database setup without actually having
70
+ one on your development machine.
71
+
72
+ === Forcing the master for certain actions
73
+
74
+ Just add this to your controller:
75
+
76
+ around_filter(:only => :foo_action) { |c,a| MyModel.connection.with_master { a.call } }
77
+
78
+ === Forcing the master for certain models
79
+
80
+ In your environment.rb or an initializer, add:
81
+
82
+ MultiDb::ConnectionProxy.master_models = ['PaymentTransaction', ...]
83
+
84
+ === Usage outside of Rails
85
+
86
+ You can use multi_db together with other framworks or in standalone scripts.
87
+ Example:
88
+
89
+ require 'rubygems'
90
+ require 'active_record'
91
+ require 'multi_db'
92
+
93
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
94
+ ActiveRecord::Base.configurations = {
95
+ 'development' => {
96
+ 'adapter' => 'mysql',
97
+ 'host' => 'localhost',
98
+ 'username' => 'root',
99
+ 'database' => 'multi_db_test'
100
+ },
101
+ 'development_slave_database' => {
102
+ 'adapter' => 'mysql',
103
+ 'host' => 'localhost',
104
+ 'username' => 'root',
105
+ 'database' => 'multi_db_test'
106
+ }
107
+ }
108
+ ActiveRecord::Base.establish_connection :development
109
+ MultiDb::ConnectionProxy.setup!
110
+
111
+ class MyModel < ActiveRecord::Base
112
+ # ...
113
+ end
114
+
115
+ # ...
116
+
117
+ Note that the configurations hash should contain strings as keys instead of symbols.
63
118
 
64
119
  === Differences to "masochism":
65
120
 
66
- * Support for multiple slave connections (round robin)
67
- * It sends anything except "select ..." queries to the master, instead of
68
- sending only specific things to the master and anything "else" to the slave,
69
- which is a lot more dangerous (e.g. "execute" wasn't sent to the master in
70
- earlier versions of masochism)
71
- * It sends everything coming from AR-Observers to the master, to avoid race
72
- conditions (idea from one of the commenters on a blog entry about masochism)
73
- * It uses its own query cache (with masochism, the slave's cache isn't emptied
74
- when there are changes on the master)
121
+ * Supports multiple slave databases (round robin)
122
+ * It sends everything except "select ..." queries to the master, instead of
123
+ sending only specific things to the master and anything "else" to the slave.
124
+ This avoids accidential writes to the master when there are API changes in
125
+ ActiveRecord which haven't been picked up by multi_db yet.
126
+ Note that this behaviour will also always send helper methods like "+quote+" or
127
+ "<tt>add_limit!</tt>" to the master connection object, which doesn't add any
128
+ more load on the master, as these methods don't communicate with the db server
129
+ itself.
130
+ * It uses its own query cache as the slave's cache isn't emptied when there are
131
+ changes on the master
75
132
  * It supports immediate failover for slave connections
76
133
  * It will wait some time before trying to query a failed slave database again
77
- * It supports nested "with_master"-blocks (in masochism, nesting such blocks
78
- would unexpectedly switch you to the slave again)
79
- * It schedules a reconnect on the master connection to avoid problems
80
- with virtual, migrating IPs for the master (e.g. multi-master HA setups)
81
- * It's possible to specify slave_database instead of master_database which
82
- makes migration between with and without multi_db less dangerous
83
- * It allows environment specific settings for different slave setups
84
- * It doesn't come with set_to_master! and set_to_slave!, as these are
85
- considered dangerous (and make no sense) in a multi-slave setup. Instead of
86
- set_to_master!, use with_master { code }
87
-
134
+ * It supports nesting "with_master"-blocks, without unexpectedly switching you
135
+ back to the slave again
136
+ * It schedules a reconnect of the master connection if statements fail there.
137
+ This might help with HA setups using virtual IPs (a test setup would be nice
138
+ to verify this)
139
+ * You specify slave databases in the configuration instead of specifying an extra
140
+ master database. This makes disabling or removing multi_db less dangerous.
141
+ * There are no <tt>set_to_master!</tt> and <tt>set_to_slave!</tt> methods, just
142
+ <tt>with_master(&block)</tt>
143
+ * All proxied methods are dynamically generated for better performance
88
144
 
89
145
  === Contributors
90
146
 
91
- * Matt Conway http://github.com/wr0ngway
92
- * Matthias Marshall http://github.com/webops
147
+ * Matt Conway http://github.com/wr0ngway
148
+ * Matthias Marshall http://github.com/webops
93
149
 
94
150
  === Running specs
95
151
 
@@ -10,7 +10,11 @@ module MultiDb
10
10
 
11
11
  # hijack the original method
12
12
  def connection
13
- @@connection_proxy
13
+ if ConnectionProxy.master_models.include?(self.to_s)
14
+ self.retrieve_connection
15
+ else
16
+ @@connection_proxy
17
+ end
14
18
  end
15
19
  end
16
20
 
@@ -13,33 +13,28 @@ module MultiDb
13
13
  attr_accessor :current
14
14
 
15
15
  class << self
16
-
17
- # Example:
18
- # MultiDb::ConnectionProxy.configuration = {
19
- # :production_master_database => {
20
- # :adapter =>
21
- # }
22
- # }
23
- # defaults to the content of Rails.root/config/database.yml if not set and used with Rails
24
- attr_accessor :configuration
16
+
25
17
  # defaults to RAILS_ENV if multi_db is used with Rails
18
+ # defaults to 'development' when used outside Rails
26
19
  attr_accessor :environment
27
20
 
21
+ # a list of models that should always go directly to the master
22
+ #
23
+ # Example:
24
+ #
25
+ # MultiDb::ConnectionProxy.master_models = %w[PaymentTransactions]
26
+ attr_accessor :master_models
27
+
28
+ # Replaces the connection of ActiveRecord::Base with a proxy and
29
+ # establishes the connections to the slaves.
28
30
  def setup!
29
- # if no configuration was set, we assume that rails is used and load the database.yml
30
- unless self.configuration
31
- raise "RAILS_ROOT not set. Set MultiDb::ConnectionProxy.configuration manually if you use multi_db outside rails" unless defined?(RAILS_ROOT)
32
- self.configuration = YAML.load(ERB.new(File.read(File.join(RAILS_ROOT, 'config', 'database.yml'))).result)
33
- end
34
- unless self.environment
35
- raise "RAILS_ENV not set. Set MultiDb::ConnectionProxy.environment manually if you use multi_db outside rails" unless defined?(RAILS_ENV)
36
- self.environment = RAILS_ENV
37
- end
31
+ self.master_models ||= []
32
+ self.environment ||= (defined?(RAILS_ENV) ? RAILS_ENV : 'development')
33
+
38
34
  master = ActiveRecord::Base
39
- master.send :include, MultiDb::ActiveRecordExtensions
40
35
  slaves = init_slaves
41
- raise "No slaves defined in database configuration" if slaves.empty?
42
- slaves.each {|slave| slave.send :include, MultiDb::ActiveRecordExtensions}
36
+ raise "No slaves databases defined for environment: #{self.environment}" if slaves.empty?
37
+ master.send :include, MultiDb::ActiveRecordExtensions
43
38
  ActiveRecord::Observer.send :include, MultiDb::ObserverExtensions
44
39
  master.connection_proxy = new(master, slaves)
45
40
  master.logger.info("** multi_db with master and #{slaves.length} slave#{"s" if slaves.length > 1} loaded.")
@@ -54,7 +49,7 @@ module MultiDb
54
49
  # These would be available later as MultiDb::SlaveDatabaseSomeserver
55
50
  def init_slaves
56
51
  returning([]) do |slaves|
57
- self.configuration.keys.each do |name|
52
+ ActiveRecord::Base.configurations.keys.each do |name|
58
53
  if name.to_s =~ /#{self.environment}_(slave_database.*)/
59
54
  MultiDb.module_eval %Q{
60
55
  class #{$1.camelize} < ActiveRecord::Base
data/multi_db.gemspec CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{multi_db}
5
- s.version = "0.1.1"
5
+ s.version = "0.1.2"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Maximilian Sch\303\266fmann"]
9
9
  s.date = %q{2009-01-29}
10
10
  s.description = "Connection proxy for ActiveRecord for single master / multiple slave database deployments"
11
11
  s.email = "max@pragmatic-it.de"
12
- s.extra_rdoc_files = ["lib/multi_db/active_record_extensions.rb", "lib/multi_db/connection_proxy.rb", "lib/multi_db/observer_extensions.rb", "lib/multi_db/query_cache_compat.rb", "lib/multi_db/scheduler.rb", "LICENSE", "README.rdoc"]
12
+ s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
13
13
  s.files = ["lib/multi_db.rb", "lib/multi_db/active_record_extensions.rb", "lib/multi_db/connection_proxy.rb", "lib/multi_db/observer_extensions.rb", "lib/multi_db/query_cache_compat.rb", "lib/multi_db/scheduler.rb", "LICENSE", "README.rdoc", "spec/config/database.yml", "spec/connection_proxy_spec.rb", "spec/scheduler_spec.rb", "spec/spec_helper.rb", "multi_db.gemspec"]
14
14
  s.has_rdoc = true
15
15
  s.homepage = "http://github.com/schoefmax/multi_db"
@@ -8,12 +8,21 @@ require MULTI_DB_SPEC_DIR + '/../lib/multi_db/connection_proxy'
8
8
  RAILS_ROOT = MULTI_DB_SPEC_DIR
9
9
 
10
10
  describe MultiDb::ConnectionProxy do
11
-
11
+
12
12
  before(:all) do
13
- ActiveRecord::Base.establish_connection(MULTI_DB_SPEC_CONFIG['test'])
13
+ ActiveRecord::Base.configurations = MULTI_DB_SPEC_CONFIG
14
+ ActiveRecord::Base.establish_connection :test
15
+ class MasterModel < ActiveRecord::Base; end
16
+ @sql = 'SELECT 1 + 1 FROM DUAL'
17
+ end
18
+
19
+ before(:each) do
14
20
  MultiDb::ConnectionProxy.setup!
15
21
  @proxy = ActiveRecord::Base.connection
16
- @sql = 'SELECT 1 + 1 FROM DUAL'
22
+ end
23
+
24
+ it 'AR::B#connection should return an instance of MultiDb::ConnectionProxy' do
25
+ ActiveRecord::Base.connection.should be_a(MultiDb::ConnectionProxy)
17
26
  end
18
27
 
19
28
  it "should generate classes for each entry in the database.yml" do
@@ -114,6 +123,18 @@ describe MultiDb::ConnectionProxy do
114
123
  @proxy.master.retrieve_connection.should_receive(:insert).and_return(1)
115
124
  @proxy.insert(@sql)
116
125
  end
126
+
127
+ it 'should always use the master database for models configured as master models' do
128
+ MultiDb::SlaveDatabase2.retrieve_connection.should_receive(:select_all).once.and_return([])
129
+ MasterModel.connection.should == @proxy
130
+ MasterModel.first
131
+
132
+ MultiDb::ConnectionProxy.master_models = ['MasterModel']
133
+ MasterModel.connection.should == @proxy.master.retrieve_connection
134
+ MultiDb::SlaveDatabase1.retrieve_connection.should_not_receive(:select_all)
135
+ MasterModel.retrieve_connection.should_receive(:select_all).once.and_return([])
136
+ MasterModel.first
137
+ end
117
138
 
118
139
  end
119
140
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schoefmax-multi_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Maximilian Sch\xC3\xB6fmann"
@@ -28,11 +28,6 @@ executables: []
28
28
  extensions: []
29
29
 
30
30
  extra_rdoc_files:
31
- - lib/multi_db/active_record_extensions.rb
32
- - lib/multi_db/connection_proxy.rb
33
- - lib/multi_db/observer_extensions.rb
34
- - lib/multi_db/query_cache_compat.rb
35
- - lib/multi_db/scheduler.rb
36
31
  - LICENSE
37
32
  - README.rdoc
38
33
  files: