Skip to content

Commit e8c61b6

Browse files
committed
Adds additional database-specific rake tasks for multi-database users
1 parent 543b865 commit e8c61b6

File tree

4 files changed

+262
-0
lines changed

4 files changed

+262
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
* Add additional database-specific rake tasks for multi-database users
2+
3+
Previously, `rails db:create`, `rails db:drop`, and `rails db:migrate` were the only rails tasks that could operate on a single
4+
database. For example:
5+
6+
```
7+
rails db:create
8+
rails db:create:primary
9+
rails db:create:animals
10+
rails db:drop
11+
rails db:drop:primary
12+
rails db:drop:animals
13+
rails db:migrate
14+
rails db:migrate:primary
15+
rails db:migrate:animals
16+
```
17+
18+
With these changes, `rails db:schema:dump`, `rails db:schema:load`, `rails db:structure:dump`, `rails db:structure:load` and
19+
`rails db:test:prepare` can additionally operate on a single database. For example:
20+
21+
```
22+
rails db:schema:dump
23+
rails db:schema:dump:primary
24+
rails db:schema:dump:animals
25+
rails db:schema:load
26+
rails db:schema:load:primary
27+
rails db:schema:load:animals
28+
rails db:structure:dump
29+
rails db:structure:dump:primary
30+
rails db:structure:dump:animals
31+
rails db:structure:load
32+
rails db:structure:load:primary
33+
rails db:structure:load:animals
34+
rails db:test:prepare
35+
rails db:test:prepare:primary
36+
rails db:test:prepare:animals
37+
```
38+
39+
*Kyle Thompson*
40+
141
* Add support for `strict_loading` mode on association declarations.
242
343
Raise an error if attempting to load a record from an association that has been marked as `strict_loading` unless it was explicitly eager loaded.

activerecord/lib/active_record/railties/databases.rake

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,28 @@ db_namespace = namespace :db do
403403
db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby
404404
end
405405

406+
namespace :dump do
407+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
408+
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record for #{spec_name} database"
409+
task spec_name => :load_config do
410+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, spec_name: spec_name)
411+
ActiveRecord::Base.establish_connection(db_config)
412+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, :ruby)
413+
db_namespace["schema:dump:#{spec_name}"].reenable
414+
end
415+
end
416+
end
417+
418+
namespace :load do
419+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
420+
desc "Loads a schema.rb file into the #{spec_name} database"
421+
task spec_name => :load_config do
422+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, spec_name: spec_name)
423+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, :ruby, ENV["SCHEMA"])
424+
end
425+
end
426+
end
427+
406428
namespace :cache do
407429
desc "Creates a db/schema_cache.yml file."
408430
task dump: :load_config do
@@ -453,6 +475,28 @@ db_namespace = namespace :db do
453475
task load_if_sql: ["db:create", :environment] do
454476
db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql
455477
end
478+
479+
namespace :dump do
480+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
481+
desc "Dumps the #{spec_name} database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
482+
task spec_name => :load_config do
483+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, spec_name: spec_name)
484+
ActiveRecord::Base.establish_connection(db_config)
485+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, :sql)
486+
db_namespace["structure:dump:#{spec_name}"].reenable
487+
end
488+
end
489+
end
490+
491+
namespace :load do
492+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
493+
desc "Recreates the #{spec_name} database from the structure.sql file"
494+
task spec_name => :load_config do
495+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, spec_name: spec_name)
496+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, :sql, ENV["SCHEMA"])
497+
end
498+
end
499+
end
456500
end
457501

458502
namespace :test do
@@ -501,6 +545,59 @@ db_namespace = namespace :db do
501545
db_namespace["test:load"].invoke
502546
end
503547
end
548+
549+
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |spec_name|
550+
# desc "Recreate the #{spec_name} test database"
551+
namespace :load do
552+
task spec_name => "db:test:purge:#{spec_name}" do
553+
case ActiveRecord::Base.schema_format
554+
when :ruby
555+
db_namespace["test:load_schema:#{spec_name}"].invoke
556+
when :sql
557+
db_namespace["test:load_structure:#{spec_name}"].invoke
558+
end
559+
end
560+
end
561+
562+
# desc "Recreate the #{spec_name} test database from an existent schema.rb file"
563+
namespace :load_schema do
564+
task spec_name => "db:test:purge:#{spec_name}" do
565+
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
566+
ActiveRecord::Schema.verbose = false
567+
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :ruby)
568+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: spec_name)
569+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, :ruby, filename)
570+
ensure
571+
if should_reconnect
572+
ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)
573+
end
574+
end
575+
end
576+
577+
# desc "Recreate the #{spec_name} test database from an existent structure.sql file"
578+
namespace :load_structure do
579+
task spec_name => "db:test:purge:#{spec_name}" do
580+
filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(spec_name, :sql)
581+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: spec_name)
582+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, :sql, filename)
583+
end
584+
end
585+
586+
# desc "Empty the #{spec_name} test database"
587+
namespace :purge do
588+
task spec_name => %w(load_config check_protected_environments) do
589+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", spec_name: spec_name)
590+
ActiveRecord::Tasks::DatabaseTasks.purge(db_config)
591+
end
592+
end
593+
594+
# desc 'Load the #{spec_name} database test schema'
595+
namespace :prepare do
596+
task spec_name => :load_config do
597+
db_namespace["test:load:#{spec_name}"].invoke
598+
end
599+
end
600+
end
504601
end
505602
end
506603

guides/source/active_record_multiple_databases.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ rails db:migrate:primary # Migrate primary database for current
148148
rails db:migrate:status # Display status of migrations
149149
rails db:migrate:status:animals # Display status of migrations for animals database
150150
rails db:migrate:status:primary # Display status of migrations for primary database
151+
rails db:schema:dump # Creates a db/schema.rb file that is portable against any DB supported ...
152+
rails db:schema:dump:animals # Creates a db/schema.rb file that is portable against any DB supported ...
153+
rails db:schema:dump:primary # Creates a db/schema.rb file that is portable against any DB supported ...
154+
rails db:schema:load # Loads a schema.rb file into the database
155+
rails db:schema:load:animals # Loads a schema.rb file into the animals database
156+
rails db:schema:load:primary # Loads a schema.rb file into the primary database
157+
rails db:structure:dump # Dumps the database structure to db/structure.sql. Specify another file ...
158+
rails db:structure:dump:animals # Dumps the animals database structure to sdb/structure.sql. Specify another ...
159+
rails db:structure:dump:primary # Dumps the primary database structure to db/structure.sql. Specify another ...
160+
rails db:structure:load # Recreates the databases from the structure.sql file
161+
rails db:structure:load:animals # Recreates the animals database from the structure.sql file
162+
rails db:structure:load:primary # Recreates the primary database from the structure.sql file
151163
```
152164

153165
Running a command like `rails db:create` will create both the primary and animals databases.

railties/test/application/rake/multi_dbs_test.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,83 @@ def db_migrate_and_schema_dump_and_load(format)
112112
end
113113
end
114114

115+
def db_migrate_and_schema_dump_and_load_one_database(format, database)
116+
Dir.chdir(app_path) do
117+
generate_models_for_animals
118+
rails "db:migrate:#{database}", "db:#{format}:dump:#{database}"
119+
120+
if format == "schema"
121+
if database == "primary"
122+
schema_dump = File.read("db/#{format}.rb")
123+
assert_not(File.exist?("db/animals_#{format}.rb"))
124+
assert_match(/create_table \"books\"/, schema_dump)
125+
else
126+
assert_not(File.exist?("db/#{format}.rb"))
127+
schema_dump_animals = File.read("db/animals_#{format}.rb")
128+
assert_match(/create_table \"dogs\"/, schema_dump_animals)
129+
end
130+
else
131+
if database == "primary"
132+
schema_dump = File.read("db/#{format}.sql")
133+
assert_not(File.exist?("db/animals_#{format}.sql"))
134+
assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, schema_dump)
135+
else
136+
assert_not(File.exist?("db/#{format}.sql"))
137+
schema_dump_animals = File.read("db/animals_#{format}.sql")
138+
assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"dogs\"/, schema_dump_animals)
139+
end
140+
end
141+
142+
rails "db:#{format}:load:#{database}"
143+
144+
ar_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip }
145+
animals_tables = lambda { rails("runner", "p AnimalsBase.connection.tables").strip }
146+
147+
if database == "primary"
148+
assert_equal '["schema_migrations", "ar_internal_metadata", "books"]', ar_tables[]
149+
assert_equal "[]", animals_tables[]
150+
else
151+
assert_equal "[]", ar_tables[]
152+
assert_equal '["schema_migrations", "ar_internal_metadata", "dogs"]', animals_tables[]
153+
end
154+
end
155+
end
156+
157+
def db_test_prepare_spec_name(spec_name, schema_format)
158+
add_to_config "config.active_record.schema_format = :#{schema_format}"
159+
require "#{app_path}/config/environment"
160+
161+
Dir.chdir(app_path) do
162+
generate_models_for_animals
163+
164+
if schema_format == "ruby"
165+
dump_command = "db:schema:dump:#{spec_name}"
166+
else
167+
dump_command = "db:structure:dump:#{spec_name}"
168+
end
169+
170+
rails("db:migrate:#{spec_name}", dump_command)
171+
172+
output = rails("db:test:prepare:#{spec_name}", "--trace")
173+
if schema_format == "ruby"
174+
assert_match(/Execute db:test:load_schema:#{spec_name}/, output)
175+
else
176+
assert_match(/Execute db:test:load_structure:#{spec_name}/, output)
177+
end
178+
179+
ar_tables = lambda { rails("runner", "-e", "test", "p ActiveRecord::Base.connection.tables").strip }
180+
animals_tables = lambda { rails("runner", "-e", "test", "p AnimalsBase.connection.tables").strip }
181+
182+
if spec_name == "primary"
183+
assert_equal ["schema_migrations", "ar_internal_metadata", "books"].sort, JSON.parse(ar_tables[]).sort
184+
assert_equal "[]", animals_tables[]
185+
else
186+
assert_equal "[]", ar_tables[]
187+
assert_equal ["schema_migrations", "ar_internal_metadata", "dogs"].sort, JSON.parse(animals_tables[]).sort
188+
end
189+
end
190+
end
191+
115192
def db_migrate_namespaced(namespace)
116193
Dir.chdir(app_path) do
117194
generate_models_for_animals
@@ -258,6 +335,42 @@ def generate_models_for_animals
258335
db_migrate_and_schema_dump_and_load "structure"
259336
end
260337

338+
test "db:migrate:spec_name and db:schema:dump:spec_name and db:schema:load:spec_name works for the primary database" do
339+
require "#{app_path}/config/environment"
340+
db_migrate_and_schema_dump_and_load_one_database("schema", "primary")
341+
end
342+
343+
test "db:migrate:spec_name and db:schema:dump:spec_name and db:schema:load:spec_name works for the animals database" do
344+
require "#{app_path}/config/environment"
345+
db_migrate_and_schema_dump_and_load_one_database("schema", "animals")
346+
end
347+
348+
test "db:migrate:spec_name and db:structure:dump:spec_name and db:structure:load:spec_name works for the primary database" do
349+
require "#{app_path}/config/environment"
350+
db_migrate_and_schema_dump_and_load_one_database("structure", "primary")
351+
end
352+
353+
test "db:migrate:spec_name and db:structure:dump:spec_name and db:structure:load:spec_name works for the animals database" do
354+
require "#{app_path}/config/environment"
355+
db_migrate_and_schema_dump_and_load_one_database("structure", "animals")
356+
end
357+
358+
test "db:test:prepare:spec_name works for the primary database with a ruby schema" do
359+
db_test_prepare_spec_name("primary", "ruby")
360+
end
361+
362+
test "db:test:prepare:spec_name works for the animals database with a ruby schema" do
363+
db_test_prepare_spec_name("animals", "ruby")
364+
end
365+
366+
test "db:test:prepare:spec_name works for the primary database with a sql schema" do
367+
db_test_prepare_spec_name("primary", "sql")
368+
end
369+
370+
test "db:test:prepare:spec_name works for the animals database with a sql schema" do
371+
db_test_prepare_spec_name("animals", "sql")
372+
end
373+
261374
test "db:migrate:namespace works" do
262375
require "#{app_path}/config/environment"
263376
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|

0 commit comments

Comments
 (0)