DEC.
1

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?

0 comments. post a comment 我要评论