multidb 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +65 -0
  8. data/lib/multi_db/action_controller_patches.rb +53 -0
  9. data/lib/multi_db/active_record_patches.rb +94 -0
  10. data/lib/multi_db/after_initialize_patches.rb +3 -0
  11. data/lib/multi_db/engine.rb +28 -0
  12. data/lib/multi_db/multidb.rake +360 -0
  13. data/lib/multi_db/organization.rb +60 -0
  14. data/lib/multi_db/organization_host.rb +7 -0
  15. data/lib/multidb.rb +10 -0
  16. data/multidb.gemspec +26 -0
  17. data/testapp_mysql2/.simplecov +3 -0
  18. data/testapp_mysql2/Gemfile +25 -0
  19. data/testapp_mysql2/Gemfile.lock +148 -0
  20. data/testapp_mysql2/Rakefile +7 -0
  21. data/testapp_mysql2/app/assets/javascripts/application.js +15 -0
  22. data/testapp_mysql2/app/assets/stylesheets/application.css +13 -0
  23. data/testapp_mysql2/app/controllers/application_controller.rb +3 -0
  24. data/testapp_mysql2/app/models/organization.rb +14 -0
  25. data/testapp_mysql2/app/views/layouts/application.html.erb +14 -0
  26. data/testapp_mysql2/config/application.rb +62 -0
  27. data/testapp_mysql2/config/boot.rb +6 -0
  28. data/testapp_mysql2/config/database.yml +17 -0
  29. data/testapp_mysql2/config/environment.rb +5 -0
  30. data/testapp_mysql2/config/environments/development.rb +37 -0
  31. data/testapp_mysql2/config/environments/production.rb +67 -0
  32. data/testapp_mysql2/config/environments/test.rb +37 -0
  33. data/testapp_mysql2/config/initializers/backtrace_silencers.rb +7 -0
  34. data/testapp_mysql2/config/initializers/inflections.rb +15 -0
  35. data/testapp_mysql2/config/initializers/mime_types.rb +5 -0
  36. data/testapp_mysql2/config/initializers/secret_token.rb +7 -0
  37. data/testapp_mysql2/config/initializers/session_store.rb +8 -0
  38. data/testapp_mysql2/config/initializers/wrap_parameters.rb +14 -0
  39. data/testapp_mysql2/config/routes.rb +58 -0
  40. data/testapp_mysql2/config.ru +4 -0
  41. data/testapp_mysql2/db/migrate/master/20140110031857_add_organizations_tables.rb +22 -0
  42. data/testapp_mysql2/db/migrate/sessions/20140110031856_add_sessions_table.rb +14 -0
  43. data/testapp_mysql2/db/schema_master.rb +35 -0
  44. data/testapp_mysql2/db/schema_organization.rb +16 -0
  45. data/testapp_mysql2/db/schema_sessions.rb +26 -0
  46. data/testapp_mysql2/log/.gitkeep +0 -0
  47. data/testapp_mysql2/script/rails +6 -0
  48. data/testapp_mysql2/spec/models/organization_spec.rb +61 -0
  49. data/testapp_mysql2/spec/spec_helper.rb +24 -0
  50. metadata +112 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 871069e0b89ed728780ae50f13950dc1e8e1c6db
4
+ data.tar.gz: 56645bb4e9fd29f8f696e6e2534f963ce414dc62
5
+ SHA512:
6
+ metadata.gz: 67773f48625923f72efa87033d8e988ef36679c58d7ad928c1b79abd13722c54b14590c390c6e1d0c4542cb420199d8a709776ebdfbe2f0ef564086157909b74
7
+ data.tar.gz: 8ca58cc43cc994807fb72979866defbaf325f861f77b5ab2809c55a08c10b17673b1bead7a26e4482e0aaf85bff395cb5dd64093e9241e7800144670bda76dd2
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+
3
+ # Ignore .lock file as this is a gem
4
+ Gemfile.lock
5
+
6
+ # Ignore gem
7
+ multidb-*.gem
8
+
9
+ # Ignore all logfiles and tempfiles.
10
+ testapp_*/coverage
11
+ testapp_*/log/*.log
12
+ testapp_*/tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --drb
3
+ --format documentation
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Aaron Namba
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.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ #About MultiDB
2
+
3
+ MultiDB is a multitenant extension for Rails (or just ActiveRecord) that allows you to isolate each tenant into its own individual database without requiring major changes to your application code.
4
+
5
+ MultiDB is _not_ meant for systems that may have large numbers of tenants; you wouldn't want to have that many databases. It was designed for a system with 100-200 tenants, with the option to go to 1,000, and an absolute upper limit of 10,000. If you anticipate having more, MultiDB may not be an appropriate solution. (MySQL itself does not specify an upper limit on the number of databases, but it will be constrained by open file limits and, in some cases, directory entry limits.)
6
+
7
+ To minimize API changes for you, MultiDB patches ActiveRecord, ActionController and their associated rake tasks as needed to enable database switching at appropriate times and add support for three sets of schemas and migrations. ActiveRecord::SessionStore::Session has also been patched.
8
+
9
+ Internally, MultiDB refers to tenants as organizations. In addition to the organization databases, two additional databases are created: one is just for sessions, and the other is referred to as the master database, which houses the organizations table (tenants), and all other tables not associated with a particular organization. (Yes, it seems like these two additional databases could be combined, but it is a design decision intended to keep all essential data out of the default database. More on this later.)
10
+
11
+ MultiDB, when used with Rails (ActionController), determines which database to connect to at the beginning of each request by checking for `request.host`, `params[:org_code]`, then `session[:org_code]`. In a test environment, it can will also check the environment variable `RAILS_ORG`. If no organization code is found in any of those places, the sessions database is used (which is one reason it is important that no actual data be stored there).
12
+
13
+
14
+ ## Compatibility
15
+
16
+ ### Rails & ActiveRecord
17
+ MultiDB 3.2 works with Rails 3.2. A new branch will be created to work with Rails 4.
18
+
19
+ The concept should work with just ActiveRecord (no Rails), but this use case has not been tested. (Pull requests welcome.)
20
+
21
+ ### Database Adapters
22
+ Warning: MultiDB works only with mysql2 at present. If you are handy with Ruby, please help to add support for more adapters. See the "Contributing" section below.
23
+
24
+ MultiDB is known to work with [Makara](https://github.com/taskrabbit/makara) in a production environment.
25
+
26
+
27
+ ##Get Started With MultiDB
28
+
29
+ Install the gem:
30
+
31
+ gem install multidb
32
+
33
+ Or add it to your Gemfile:
34
+
35
+ gem 'multidb'
36
+
37
+ Then have your Organization class inherit from MultiDB::Organization. This gets you:
38
+
39
+ Organization#connect(set_env = false)
40
+ Organization#create_database
41
+ Organization#drop_database!
42
+
43
+ Your table will need to have columns for `code` (string) and `active` (boolean), and to use the request.host feature, you will need organization_hosts as well. Recommended migration:
44
+
45
+ create_table "organization_hosts", :force => true do |t|
46
+ t.integer "organization_id", :null => false
47
+ t.string "host", :null => false
48
+ t.datetime "created_at", :null => false
49
+ t.datetime "updated_at", :null => false
50
+ end
51
+ add_index "organization_hosts", ["host"], :name => "index_organization_hosts_on_host", :unique => true
52
+
53
+ create_table "organizations", :force => true do |t|
54
+ t.string "name"
55
+ t.string "code", :null => false
56
+ t.boolean "active", :default => true, :null => false
57
+ t.datetime "created_at", :null => false
58
+ t.datetime "updated_at", :null => false
59
+ end
60
+ add_index "organizations", ["code"], :name => "index_organizations_on_code", :unique => true
61
+
62
+
63
+ ## Contributing
64
+
65
+ Pull requests welcome. Don't forget tests! When adding support for a new adapter, create a new Rails app for your adapter (e.g. `rails _3.2.16_ new testapp_postgresql`), customize database.yml and other files as needed, then add specs to that app.
@@ -0,0 +1,53 @@
1
+ class ActionController::Base
2
+ prepend_before_filter :connect_to_organization_database
3
+
4
+ # manually establish a connection to the proper database
5
+ def connect_to_organization_database
6
+ @org = nil
7
+
8
+ # request is first priority
9
+ if params[:org_code]
10
+ if session[:org_code] && session[:org_code] != params[:org_code]
11
+ reset_session
12
+ end
13
+ @org = MultiDB::Organization.active.where(:code => params[:org_code]).first
14
+ end
15
+
16
+ # try hostname if we don't already have a code in the session
17
+ if !@org && !session[:org_code] && request && request.host
18
+ @org ||= MultiDB::Organization.active.where(:code => $1.gsub('-', '_')).first if request.host =~ /^([-\w\d]+)/
19
+ @org ||= MultiDB::Organization.active.includes(:hosts).where('organization_hosts.host = ?', request.host).first
20
+ end
21
+
22
+ if @org
23
+ if session[:org_code] != @org.code
24
+ session[:org_code] = @org.code
25
+ session[:org_name] = @org.name
26
+ end
27
+ end
28
+
29
+ if session[:org_code]
30
+ @org ||= MultiDB::Organization.active.where(:code => session[:org_code]).first
31
+ if @org
32
+ ActiveRecord::Base.connect_to_organization(session[:org_code], true)
33
+ return @org
34
+ end
35
+ end
36
+
37
+ if Rails.env.test? && ENV['RAILS_ORG']
38
+ @org ||= MultiDB::Organization.active.where(:code => ENV['RAILS_ORG']).first
39
+ if @org
40
+ ActiveRecord::Base.connect_to_organization(session[:org_code], true)
41
+ return @org
42
+ end
43
+ end
44
+
45
+ # if we don't issue an establish_connection by now, connect to default db (sessions)
46
+ session[:org_code] = session[:org_name] = nil
47
+ ActiveRecord::Base.connect_to_sessions
48
+ end
49
+
50
+ def connect_to_master_database
51
+ ActiveRecord::Base.connect_to_master
52
+ end
53
+ end
@@ -0,0 +1,94 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class << self
4
+
5
+ def master_configuration(env = nil)
6
+ env ||= Rails.env
7
+
8
+ # use master db configuration in config/database.yml if present
9
+ configurations["master_#{env}"] or Proc.new {
10
+ c = configurations[env].dup
11
+ c['database'] += '_master'
12
+ c
13
+ }.call
14
+ end
15
+
16
+ def connect_to_sessions
17
+ config = configurations[Rails.env || 'development']
18
+ self.establish_connection(config)
19
+ end
20
+
21
+ def connect_to_master
22
+ self.establish_connection(master_configuration)
23
+ end
24
+
25
+ def connect_to_organization(org = nil, set_env = false)
26
+ org_code = org.is_a?(MultiDB::Organization) ? org.code : org
27
+ org_code ||= ENV['RAILS_ORG'] || 'org1'
28
+
29
+ config = ActiveRecord::Base.configurations[Rails.env]
30
+ self.establish_connection(config.merge('database' => "#{config['database']}_#{org_code}"))
31
+
32
+ begin
33
+ ActiveRecord::Base.connection.tables
34
+ rescue Mysql2::Error
35
+ Rails.logger.error "Couldn't connect to org db for #{org_code}"
36
+ return false
37
+ end
38
+
39
+ ENV['RAILS_ORG'] = org_code if set_env
40
+ org_code
41
+ end
42
+ end
43
+ end
44
+
45
+ class Migration
46
+
47
+ alias_method :migrate_without_multidb, :migrate
48
+ def migrate(direction)
49
+ if ENV['RAILS_ORG'] == 'master'
50
+ Base.connect_to_master
51
+ elsif ENV['RAILS_ORG'] == 'sessions'
52
+ Base.connect_to_sessions
53
+ else
54
+ Base.connect_to_organization
55
+ end
56
+
57
+ migrate_without_multidb(direction)
58
+ end
59
+
60
+ end
61
+
62
+ class Migrator
63
+
64
+ alias_method :initialize_without_multidb, :initialize
65
+ def initialize(direction, migrations_paths, target_version = nil)
66
+ if ENV['RAILS_ORG'] == 'master'
67
+ Base.connect_to_master
68
+ elsif ENV['RAILS_ORG'] == 'sessions'
69
+ Base.connect_to_sessions
70
+ else
71
+ Base.connect_to_organization
72
+ end
73
+
74
+ initialize_without_multidb(direction, migrations_paths, target_version)
75
+ end
76
+
77
+ class << self
78
+
79
+ def migrations_paths
80
+ @migrations_paths ||= ['db/migrate']
81
+ # just to not break things if someone uses: migration_path = some_string
82
+ paths = Array.wrap(@migrations_paths)
83
+
84
+ case ENV['RAILS_ORG']
85
+ when 'sessions', 'master'
86
+ paths.map { |path| "#{path}/#{ENV['RAILS_ORG']}" }
87
+ else
88
+ paths.map { |path| "#{path}/org" }
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ class ActiveRecord::SessionStore::Session < ActiveRecord::Base
2
+ connect_to_sessions
3
+ end
@@ -0,0 +1,28 @@
1
+ module MultiDB
2
+ class Engine < Rails::Engine
3
+ engine_name "multidb"
4
+
5
+ config.app_root = root
6
+ config.autoload_paths += Dir["#{config.root}/lib/**/"]
7
+
8
+ ActiveSupport.on_load(:active_record) do
9
+ require 'multi_db/active_record_patches'
10
+ end
11
+
12
+ ActiveSupport.on_load(:action_controller) do
13
+ require 'multi_db/action_controller_patches'
14
+ end
15
+
16
+ ActiveSupport.on_load(:after_initialize) do
17
+ require 'multi_db/after_initialize_patches'
18
+ end
19
+ end
20
+ end
21
+
22
+ module MultiDB
23
+ class Railtie < Rails::Railtie
24
+ rake_tasks do
25
+ load "multi_db/multidb.rake"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,360 @@
1
+ #
2
+ # Based on activerecord-3.2.15/lib/active_record/railties/databases.rake
3
+ # Last updated 2013-11-19 by Aaron Namba
4
+ #
5
+ # AKN: Warning! All code to support db engines other than mysql has been deleted,
6
+ # since I know it will never receive proper testing.
7
+ #
8
+
9
+ tasks = Rake.application.instance_variable_get '@tasks'
10
+ tasks.delete 'db:migrate'
11
+ tasks.delete 'db:migrate:up'
12
+ tasks.delete 'db:migrate:down'
13
+ tasks.delete 'db:rollback'
14
+ tasks.delete 'db:forward'
15
+ tasks.delete 'db:schema:dump'
16
+ tasks.delete 'db:schema:load'
17
+ tasks.delete 'db:test:load_schema'
18
+ tasks.delete 'db:test:purge'
19
+
20
+
21
+ db_namespace = namespace :db do
22
+ namespace :create do
23
+ desc 'Runs create for sessions and master databases.'
24
+ task :multi => [:load_config] do
25
+ [ 'sessions', 'master' ].each do |org|
26
+ ENV['RAILS_ORG'] = org
27
+ db_namespace[:create].execute
28
+ end
29
+ end
30
+ end
31
+
32
+ namespace :drop do
33
+ desc 'Runs drop for sessions and master databases.'
34
+ task :multi => [:load_config] do
35
+ [ 'sessions', 'master' ].each do |org|
36
+ ENV['RAILS_ORG'] = org
37
+ db_namespace[:drop].execute
38
+ end
39
+ end
40
+ end
41
+
42
+ namespace :setup do
43
+ desc 'Runs setup for sessions and master databases.'
44
+ task :multi => [:load_config] do
45
+ [ 'sessions', 'master' ].each do |org|
46
+ ENV['RAILS_ORG'] = org
47
+ db_namespace[:create].execute
48
+ db_namespace['schema:load'].execute
49
+ db_namespace[:seed].execute
50
+ end
51
+ end
52
+ end
53
+
54
+ namespace :migrate do
55
+ desc 'Runs migrate for sessions, master and org databases.'
56
+ task :multi => [:environment, :load_config] do
57
+ puts "=========================================================="
58
+ puts "===== SESSIONS"
59
+ puts "=========================================================="
60
+ puts
61
+ ENV['RAILS_ORG'] = 'sessions'
62
+ db_namespace[:migrate].execute
63
+ puts
64
+
65
+ puts "=========================================================="
66
+ puts "===== MASTER"
67
+ puts "=========================================================="
68
+ puts
69
+ ENV['RAILS_ORG'] = 'master'
70
+ db_namespace[:migrate].execute
71
+ puts
72
+
73
+ puts "=========================================================="
74
+ puts "===== ORGANIZATIONS"
75
+ puts "=========================================================="
76
+ puts
77
+ ENV['RAILS_ORG'] = nil
78
+ db_namespace[:migrate].execute
79
+ puts
80
+ end
81
+
82
+ # desc 'Runs the "up" for a given migration VERSION.'
83
+ task :up => [:environment, :load_config] do
84
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
85
+ raise 'VERSION is required' unless version
86
+
87
+ if ENV['RAILS_ORG'] == 'sessions' || ENV['RAILS_ORG'] == 'all'
88
+ ActiveRecord::Base.connect_to_sessions
89
+ ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
90
+ db_namespace['_dump'].invoke
91
+ end
92
+
93
+ if ENV['RAILS_ORG'] == 'master' || ENV['RAILS_ORG'] == 'all'
94
+ ActiveRecord::Base.connect_to_master
95
+ ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
96
+ db_namespace['_dump'].invoke
97
+ end
98
+
99
+ if ENV['RAILS_ORG'] == 'all' || ![ 'sessions', 'master' ].include?(ENV['RAILS_ORG'])
100
+ schema_dumped = false
101
+ MultiDB::Organization.active.each do |org|
102
+ ActiveRecord::Base.connect_to_organization(org, true)
103
+ ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
104
+ db_namespace['_dump'].invoke unless schema_dumped
105
+ schema_dumped = true
106
+ end
107
+ end
108
+ end
109
+
110
+ # desc 'Runs the "down" for a given migration VERSION.'
111
+ task :down => [:environment, :load_config] do
112
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
113
+ raise 'VERSION is required' unless version
114
+
115
+ if ENV['RAILS_ORG'] == 'sessions'
116
+ ActiveRecord::Base.connect_to_sessions
117
+ ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
118
+ db_namespace['_dump'].invoke
119
+ end
120
+
121
+ if ENV['RAILS_ORG'] == 'master'
122
+ ActiveRecord::Base.connect_to_master
123
+ ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
124
+ db_namespace['_dump'].invoke
125
+ end
126
+
127
+ if ![ 'sessions', 'master' ].include?(ENV['RAILS_ORG'])
128
+ schema_dumped = false
129
+ MultiDB::Organization.active.each do |org|
130
+ ActiveRecord::Base.connect_to_organization(org, true)
131
+ ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
132
+ db_namespace['_dump'].invoke unless schema_dumped
133
+ schema_dumped = true
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
140
+ task :migrate => [:environment, :load_config] do
141
+ ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
142
+
143
+ if ENV['RAILS_ORG'] == 'sessions'
144
+ ActiveRecord::Base.connect_to_sessions
145
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
146
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
147
+ end
148
+ db_namespace['_dump'].invoke
149
+ end
150
+
151
+ if ENV['RAILS_ORG'] == 'master'
152
+ ActiveRecord::Base.connect_to_master
153
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
154
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
155
+ end
156
+ db_namespace['_dump'].invoke
157
+ end
158
+
159
+ if ![ 'sessions', 'master' ].include?(ENV['RAILS_ORG'])
160
+ schema_dumped = false
161
+ MultiDB::Organization.active.each do |org|
162
+ puts "== Migrating: #{org.code}"
163
+ ActiveRecord::Base.connect_to_organization(org, true)
164
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
165
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
166
+ end
167
+ db_namespace['_dump'].invoke unless schema_dumped
168
+ schema_dumped = true
169
+ end
170
+ end
171
+ end
172
+
173
+ desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
174
+ task :rollback => [:environment, :load_config] do
175
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
176
+
177
+ if ENV['RAILS_ORG'] == 'sessions'
178
+ ActiveRecord::Base.connect_to_sessions
179
+ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
180
+ db_namespace['_dump'].invoke
181
+ end
182
+
183
+ if ENV['RAILS_ORG'] == 'master'
184
+ ActiveRecord::Base.connect_to_master
185
+ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
186
+ db_namespace['_dump'].invoke
187
+ end
188
+
189
+ if ![ 'sessions', 'master' ].include?(ENV['RAILS_ORG'])
190
+ schema_dumped = false
191
+ MultiDB::Organization.active.each do |org|
192
+ puts "== Rollback: #{org.code}"
193
+ ActiveRecord::Base.connect_to_organization(org, true)
194
+ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
195
+ db_namespace['_dump'].invoke unless schema_dumped
196
+ schema_dumped = true
197
+ end
198
+ end
199
+ end
200
+
201
+ # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
202
+ task :forward => [:environment, :load_config] do
203
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
204
+
205
+ if ENV['RAILS_ORG'] == 'sessions'
206
+ ActiveRecord::Base.connect_to_sessions
207
+ ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
208
+ db_namespace['_dump'].invoke
209
+ end
210
+
211
+ if ENV['RAILS_ORG'] == 'master'
212
+ ActiveRecord::Base.connect_to_master
213
+ ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
214
+ db_namespace['_dump'].invoke
215
+ end
216
+
217
+ if ![ 'sessions', 'master' ].include?(ENV['RAILS_ORG'])
218
+ schema_dumped = false
219
+ MultiDB::Organization.active.each do |org|
220
+ ActiveRecord::Base.connect_to_organization(org, true)
221
+ ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
222
+ db_namespace['_dump'].invoke unless schema_dumped
223
+ schema_dumped = true
224
+ end
225
+ end
226
+ end
227
+
228
+ namespace :schema do
229
+ desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
230
+ task :dump => [:environment, :load_config] do
231
+ require 'active_record/schema_dumper'
232
+
233
+ databases_to_dump = case ENV['RAILS_ORG']
234
+ when nil then [ :sessions, :master, :organization ]
235
+ when 'sessions' then [ :sessions ]
236
+ when 'master' then [ :master ]
237
+ else [ :organization ]
238
+ end
239
+
240
+ databases_to_dump.each do |db|
241
+ ActiveRecord::Base.send("connect_to_#{db}")
242
+ filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema_#{db}.rb"
243
+ File.open(filename, "w:utf-8") do |file|
244
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
245
+ end
246
+ end
247
+ db_namespace['schema:dump'].reenable
248
+ end
249
+
250
+ desc 'Load a schema.rb file into the database'
251
+ task :load => [:environment, :load_config] do
252
+ databases_to_load = case ENV['RAILS_ORG']
253
+ when nil then [ :sessions, :master, :organization ]
254
+ when 'sessions' then [ :sessions ]
255
+ when 'master' then [ :master ]
256
+ else [ :organization ]
257
+ end
258
+
259
+ databases_to_load.each do |db|
260
+ ActiveRecord::Base.send("connect_to_#{db}")
261
+ file = ENV['SCHEMA'] || "#{Rails.root}/db/schema_#{db}.rb"
262
+ if File.exists?(file)
263
+ load(file)
264
+ else
265
+ abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ namespace :test do
272
+ # desc "Recreate the test database from an existent schema.rb file"
273
+ task :load_schema => 'db:test:purge' do
274
+ old_env = Rails.env
275
+ Rails.env = 'test'
276
+ ActiveRecord::Schema.verbose = false
277
+ db_namespace["schema:load"].invoke
278
+ Rails.env = old_env
279
+ end
280
+
281
+ # desc "Empty the test databases"
282
+ task :purge => [:environment, :load_config] do
283
+ abcs = ActiveRecord::Base.configurations
284
+
285
+ # sessions
286
+ ActiveRecord::Base.connect_to_sessions
287
+ ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test']))
288
+
289
+ # master
290
+ ActiveRecord::Base.connect_to_master
291
+ ActiveRecord::Base.connection.recreate_database(ActiveRecord::Base.master_configuration('test')['database'], mysql_creation_options(abcs['test']))
292
+
293
+ # org
294
+ ActiveRecord::Base.connect_to_organization
295
+ ActiveRecord::Base.connection.recreate_database(abcs['test']['database'] + '_org1', mysql_creation_options(abcs['test']))
296
+ end
297
+ end
298
+
299
+ alias :create_database_without_multidb :create_database
300
+ def create_database(_config)
301
+ is_test = ActiveRecord::Base.configurations.invert[_config] == 'test'
302
+ config = _config.dup
303
+
304
+ case ENV['RAILS_ORG']
305
+ when nil
306
+ # set default org database
307
+ config['database'] += '_org1'
308
+ when 'sessions'
309
+ # do nothing
310
+ when 'master'
311
+ config = ActiveRecord::Base.master_configuration(is_test ? 'test' : nil)
312
+ else
313
+ config['database'] += '_' + ENV['RAILS_ORG']
314
+ end
315
+
316
+ create_database_without_multidb(config)
317
+ end
318
+ end
319
+
320
+
321
+ alias :drop_database_without_multidb :drop_database
322
+ def drop_database(_config)
323
+ is_test = ActiveRecord::Base.configurations.invert[_config] == 'test'
324
+ config = _config.dup
325
+
326
+ case ENV['RAILS_ORG']
327
+ when nil
328
+ # set default org database
329
+ config['database'] += '_org1'
330
+ when 'sessions'
331
+ # do nothing
332
+ when 'master'
333
+ config = ActiveRecord::Base.master_configuration(is_test ? 'test' : nil)
334
+ else
335
+ config['database'] += '_' + ENV['RAILS_ORG']
336
+ end
337
+
338
+ drop_database_without_multidb(config)
339
+ end
340
+
341
+ # not sure yet why environment is not loaded before running create_database/drop_database
342
+ # for now, copied this in from lib/multi_db/active_record_patches.rb
343
+ module ActiveRecord
344
+ class Base
345
+ class << self
346
+
347
+ def master_configuration(env = nil)
348
+ env ||= Rails.env
349
+
350
+ # use master db configuration in config/database.yml if present
351
+ configurations["master_#{env}"] or Proc.new {
352
+ c = configurations[env].dup
353
+ c['database'] += '_master'
354
+ c
355
+ }.call
356
+ end
357
+
358
+ end
359
+ end
360
+ end