revolutionhealth-masochism 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007 Rick Olson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+ masochism
2
+ =========
3
+
4
+ <div style="width:240px; padding:2px; border:1px solid silver; float:right; margin:0 0 1em 2em; background:white">
5
+ <img src="http://farm1.static.flickr.com/111/295426387_a39c5c8954_m.jpg" alt="Scream" />
6
+ <p style="text-align:center">photo by <a href="http://flickr.com/people/alphadesigner/" title="Flickr: ArtWerk">ArtWerk</a></p>
7
+ </div>
8
+
9
+ The masochism plugin provides an easy solution for Ruby on Rails applications to work in a
10
+ replicated database environment. It works by replacing the `connection` object accessed by
11
+ ActiveRecord models by ConnectionProxy that chooses between master and slave when
12
+ executing queries. Generally all writes go to master.
13
+
14
+
15
+ Quick setup
16
+ -----------
17
+
18
+ First, setup your database.yml:
19
+
20
+ # default configuration (slave)
21
+ production: &defaults
22
+ adapter: mysql
23
+ database: app_production
24
+ username: webapp
25
+ password: ********
26
+ host: localhost
27
+
28
+ # setup for masochism (master)
29
+ master_database:
30
+ <<: *defaults
31
+ host: master.example.com
32
+
33
+ To enable masochism, this is required:
34
+
35
+ # enable masochism
36
+ ActiveReload::ConnectionProxy.setup!
37
+
38
+ Example usage:
39
+
40
+ # in environment.rb
41
+ config.after_initialize do
42
+ if Rails.env.production?
43
+ ActiveReload::ConnectionProxy::setup!
44
+ end
45
+ end
46
+
47
+
48
+ Considerations
49
+ --------------
50
+
51
+ ### Thinking Sphinx
52
+
53
+ Thinking Sphinx inspects the `connection` object to determine the database adapter.
54
+ Because masochism works by putting the connection proxy in its place, TS will be confused
55
+ about `ActiveReload::ConnectionProxy` and abort. A possible workaround is to monkeypatch TS right to **hardcode** our adapter after masochism has been enabled:
56
+
57
+ # ConnectionProxy from masochism confuses TS
58
+ ThinkingSphinx::Index.class_eval do
59
+ def adapter() :mysql end
60
+ end
61
+
62
+ ThinkingSphinx::AbstractAdapter.class_eval do
63
+ def self.detect(model)
64
+ ThinkingSphinx::MysqlAdapter
65
+ end
66
+ end
67
+
68
+ ### Litespeed web server or Phusion Passenger (mod\_rails)
69
+
70
+ If you are using the Litespeed web server or Passenger (mod\_rails), child processes are initialized on creation,
71
+ which means any setup done in an environment file will be effectively ignored. [A brief
72
+ discussion of the problem is posted here](http://litespeedtech.com/support/wiki/doku.php?id=litespeed_wiki:rails:memcache).
73
+
74
+ One solution for Litespeed/Passenger users is to check the connection at your first request and do
75
+ the `setup!` call if your connection hasn't been initialized, like:
76
+
77
+ # in ApplicationController
78
+ prepend_before_filter do |controller|
79
+ unless ActiveRecord::Base.connection.is_a? ActiveReload::ConnectionProxy
80
+ ActiveReload::ConnectionProxy.setup!
81
+ end
82
+ end
83
+
84
+
85
+ Advanced
86
+ --------
87
+
88
+ The ActiveReload::MasterDatabase model uses a 'master\_database' setting that can either be
89
+ defined for all of your environments, or for each environment as a nested declaration.
90
+
91
+ The ActiveReload::SlaveDatabase model uses a 'slave\_database' setting that can only be
92
+ defined per environment.
93
+
94
+ Example:
95
+
96
+ login: &login
97
+ adapter: postgresql
98
+ host: localhost
99
+ port: 5432
100
+
101
+ production:
102
+ database: production_slave_database_name
103
+ <<: *login
104
+
105
+ master_database:
106
+ database: production_master_database_name
107
+ <<: *login
108
+
109
+ staging:
110
+ database: staging_database_name
111
+ host: slave-db-pool.local
112
+ <<: *login
113
+ master_database:
114
+ database: staging_database_name
115
+ host: master-db-server.local
116
+ <<: *login
117
+
118
+ qa:
119
+ database: qa_master_database_name
120
+ host: qa-master
121
+ <<: *login
122
+ slave_database:
123
+ database: qa_slave_database_name
124
+ host: qa-slave
125
+ <<: *login
126
+
127
+ development: # Does not use masochism
128
+ database: development_database_name
129
+ <<: *login
130
+
131
+ If you want a model to always use the Master database, you can inherit
132
+ `ActiveReload::MasterDatabase`. Any models with their own database connection will not be
133
+ affected.
134
+
135
+ ### More control at setup
136
+
137
+ By default, masochism `setup!` is a shorthand for this:
138
+
139
+ ActiveReload::ConnectionProxy.setup_for ActiveReload::MasterDatabase, ActiveRecord::Base
140
+
141
+ The first argument is the model that has the master database connection established; the
142
+ second argument is the model whose `connection` gets hijacked by ConnectionProxy. But we
143
+ don't have to touch `ActiveRecord::Base` at all:
144
+
145
+ # set up MyMaster's connection as the master database connection for User:
146
+ ActiveReload::ConnectionProxy.setup_for MyMaster, User
147
+
148
+ ### The controller filter
149
+
150
+ If you have any actions you know require the master database for both reads and writes,
151
+ simply do the following:
152
+
153
+ # in a controller:
154
+ around_filter ActiveReload::MasterFilter, :only => [:show, :edit, :update]
@@ -0,0 +1,141 @@
1
+ module ActiveReload
2
+ class MasterDatabase < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ establish_connection configurations[Rails.env]['master_database'] || configurations['master_database'] || Rails.env
5
+ end
6
+
7
+ class SlaveDatabase < ActiveRecord::Base
8
+ self.abstract_class = true
9
+ def self.name
10
+ ActiveRecord::Base.name
11
+ end
12
+ establish_connection configurations[Rails.env]['slave_database'] || Rails.env
13
+ end
14
+
15
+ class ConnectionProxy
16
+
17
+ def initialize(master_class, slave_class)
18
+ @master = master_class
19
+ @slave = slave_class
20
+ @current = :slave
21
+ end
22
+
23
+ def master
24
+ @slave.connection_handler.retrieve_connection(@master)
25
+ end
26
+
27
+ def slave
28
+ @slave.retrieve_connection
29
+ end
30
+
31
+ def current
32
+ send @current
33
+ end
34
+
35
+ def self.setup!
36
+ if slave_defined?
37
+ setup_for ActiveReload::MasterDatabase, ActiveReload::SlaveDatabase
38
+ else
39
+ setup_for ActiveReload::MasterDatabase
40
+ end
41
+ end
42
+
43
+ def self.slave_defined?
44
+ ActiveRecord::Base.configurations[Rails.env]['slave_database']
45
+ end
46
+
47
+ def self.setup_for(master, slave = nil)
48
+ slave ||= ActiveRecord::Base
49
+ slave.send :include, ActiveRecordConnectionMethods
50
+ ActiveRecord::Observer.send :include, ActiveReload::ObserverExtensions
51
+ slave.connection_proxy = new(master, slave)
52
+ end
53
+
54
+ def with_master(to_slave = true)
55
+ set_to_master!
56
+ yield
57
+ ensure
58
+ set_to_slave! if to_slave
59
+ end
60
+
61
+ def set_to_master!
62
+ unless @current == :master
63
+ # @slave.logger.info caller.inspect
64
+ @slave.logger.info "Switching to Master"
65
+ @current = :master
66
+ end
67
+ end
68
+
69
+ def set_to_slave!
70
+ unless @current == :slave
71
+ # @master.logger.info caller.inspect
72
+ @master.logger.info "Switching to Slave"
73
+ @current = :slave
74
+ end
75
+ end
76
+
77
+ delegate :insert, :update, :delete, :create_table, :rename_table, :drop_table, :add_column, :remove_column,
78
+ :change_column, :change_column_default, :rename_column, :add_index, :remove_index, :initialize_schema_migrations_table, :select_values,
79
+ :dump_schema_information, :execute, :columns, :to => :master
80
+
81
+ def transaction(start_db_transaction = true, &block)
82
+ with_master(start_db_transaction) do
83
+ master.transaction(start_db_transaction, &block)
84
+ end
85
+ end
86
+
87
+ def method_missing(method, *args, &block)
88
+ val = current.send(method, *args, &block)
89
+ val
90
+ end
91
+ end
92
+
93
+ module ActiveRecordConnectionMethods
94
+ def self.included(base)
95
+ base.alias_method_chain :reload, :master
96
+
97
+ class << base
98
+ def connection_proxy=(proxy)
99
+ @@connection_proxy = proxy
100
+ end
101
+
102
+ # hijack the original method
103
+ #only return if current is defined
104
+ #calling retrieve_connection thru current also trigger code that
105
+ #raises a ConnectionError if the db is not created, this fixes the
106
+ #problem with running rake db:create in rails 2.2.2
107
+ def connection
108
+ @@connection_proxy if @@connection_proxy.current
109
+ end
110
+ end
111
+ end
112
+
113
+ def reload_with_master(*args, &block)
114
+ if connection.class.name == "ActiveReload::ConnectionProxy"
115
+ connection.with_master { reload_without_master }
116
+ else
117
+ reload_without_master
118
+ end
119
+ end
120
+ end
121
+
122
+ # extend observer to always use the master database
123
+ # observers only get triggered on writes, so shouldn't be a performance hit
124
+ # removes a race condition if you are using conditionals in the observer
125
+ module ObserverExtensions
126
+ def self.included(base)
127
+ base.alias_method_chain :update, :masterdb
128
+ end
129
+
130
+ # Send observed_method(object) if the method exists.
131
+ def update_with_masterdb(observed_method, object) #:nodoc:
132
+ if object.class.connection.respond_to?(:with_master)
133
+ object.class.connection.with_master do
134
+ update_without_masterdb(observed_method, object)
135
+ end
136
+ else
137
+ update_without_masterdb(observed_method, object)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveReload
2
+ # MasterFilter should be used as an around filter in your controllers that require certain actions to use the Master DB for reads as well as writes
3
+ class MasterFilter
4
+ def self.filter(controller, &block)
5
+ if ActiveRecord::Base.connection.respond_to?(:with_master)
6
+ ActiveRecord::Base.connection.with_master(&block)
7
+ else
8
+ yield block
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+
3
+ namespace :db do
4
+ task :create => :environment do
5
+ create_database(ActiveRecord::Base.configurations[RAILS_ENV]["master_database"])
6
+ end
7
+ end
8
+
9
+ require 'active_reload/connection_proxy'
10
+ require 'active_reload/master_filter'
11
+
12
+ #ActiveReload::ConnectionProxy.setup!
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revolutionhealth-masochism
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Revolution Health
8
+ autorequire: masochism
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-23 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ActiveRecord connection proxy for master/slave connections
17
+ email: rails@revolutionhealth.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.markdown
25
+ files:
26
+ - README.markdown
27
+ - lib/active_reload/connection_proxy.rb
28
+ - lib/active_reload/master_filter.rb
29
+ - lib/masochism.rb
30
+ - LICENSE
31
+ has_rdoc: true
32
+ homepage: http://github.com/revolutionhealth/masochism/
33
+ post_install_message:
34
+ rdoc_options: []
35
+
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: "0"
43
+ version:
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ requirements: []
51
+
52
+ rubyforge_project:
53
+ rubygems_version: 1.2.0
54
+ signing_key:
55
+ specification_version: 1
56
+ summary: ActiveRecord connection proxy for master/slave connections
57
+ test_files: []
58
+