Everybody knows Rails migration is awesome. It is known as a very successful pattern, and I really like it. But today I have a weird requirement which is I need to frequently drop and create table schema while testing one part of my SF Muni Tracker application, and it has nothing related to Rails. Besides I’m a super lazy developer, so I really want to have something like the Rails migration rake task, then I decided to see how Rails does it and if I can reuse some part of its code.
Basically, the database.rake file contains all the db migration magics an related classes are ActiveRecord::Base, ActiveRecord::Migration and ActiveRecord::Migrator.
The main part of db:create looks like this:
task :all => :load_config do
ActiveRecord::Base.configurations.each_value do |config|
local_database?(config) do
create_database(config)
end
end
end
create_database is a long method, but I can write something simple using ActiveRecord::Base to replace it, I have attached my code at the end of this blog.
The main part of db:drop looks like this:
task :all => :load_config do
ActiveRecord::Base.configurations.each_value do |config|
next unless config['database']
local_database?(config) do
drop_database(config)
end
end
end
Similar to create_database, I can write my own drop_database method.
db:migrate looks like this:
task :migrate => :environment do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
So all of them are not difficult, I just need to mock database configuration and reuse some ActiveRecord methods, so I implemented the following Rakefile, I used PostgreSql as an example:
require 'rubygems'
require 'rake'
namespace :db do
DB_CONFIG = { 'development' => {
'database' => 'sample',
'adapter' => 'postgresql',
'username' => 'phoenix',
'password' => '',
'host' => 'localhost',
'port' => '5432'}
}
task :load_config do
require 'active_record'
ActiveRecord::Base.configurations = DB_CONFIG
end
task :drop => :load_config do
ActiveRecord::Base.configurations.each_value { |config| drop_database(config) }
end
task :create => :load_config do
ActiveRecord::Base.configurations.each_value { |config| create_database(config) }
end
task :migrate => :load_config do
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations.values.first)
ActiveRecord::Base.logger = OpenStruct.new(:info => nil)
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
end
end
def drop_database(config)
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
ActiveRecord::Base.connection.drop_database config['database']
end
def create_database(config)
encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => encoding))
ActiveRecord::Base.establish_connection(config)
end
I believe those code maybe even simpler if you use Sqlite3. The tricky thing is, you need to mock ActiveRecord::Base.logger ,manually specify database connection info. After that you just need to create db/migrate/ directory and put all your migration files within it. You can create your own generator if you want to generate the migration, but I don’t really need it, rails_generator directory is the right place to take a look if you want to create the generator. So that’s it! Isn’t it simple?
