Interview with Fabio Akita 0
Making migrations easier 0
One of my most common problems when developing migrations for a rails project happens when I make a mistake in my migration code that causes the migration to fail to complete successfully. When this happens, it is often the case that some of the commands in your migration executed successfully. This means that when you fix your bug and try to run the migration again, it will fail because it will attempt to run the previously successful commands but will complain that the changes they refer to are already there. There are some workarounds for this, such as using the :force => true option, but even then there are some situations that are hard to recover from. I often ended up having to manually drop my database and re-create it and then run the migration again—a real pain. So I finally decided to make a rake task that would make this a lot easier.
namespace :db do
task :nuke => :environment do
abcs = ActiveRecord::Base.configurations
["development", "test"].each do |db|
case abcs[db]["adapter"]
when "oci"
ActiveRecord::Base.establish_connection(db.to_sym)
conn = ActiveRecord::Base.connection
conn.begin_db_transaction
conn.tables.each do |table|
indexes = conn.indexes(table)
indexes.each do |ind|
puts "Dropping index #{ind.name}"
conn.execute("DROP INDEX #{ind.name}")
end
puts "Dropping table #{table}"
conn.execute("DROP TABLE #{table}")
end
sql = "SELECT LOWER(sequence_name) FROM user_sequences"
sequences = conn.select_all(sql).inject([]) do |seqs, s|
seqs << s.to_a.first.last
end
sequences.each do |seq|
puts "Dropping sequence #{seq}"
conn.execute("DROP SEQUENCE #{seq}")
end
conn.commit_db_transaction
when "mysql"
ActiveRecord::Base.establish_connection(db.to_sym)
conn = ActiveRecord::Base.connection
conn.execute("DROP DATABASE #{abcs[db]["database"]}")
conn.execute("CREATE DATABASE #{abcs[db]["database"]}")
ActiveRecord::Base.establish_connection(db.to_sym)
when "sqlite", "sqlite3"
dbfile = abcs[db]["database"] || abcs[db]["dbfile"]
File.delete(dbfile) if File.exist?(dbfile)
ActiveRecord::Base.establish_connection(db.to_sym)
else
raise "Task not supported by '#{abcs[db]["adapter"]}'"
end
ENV['RAILS_ENV'] = db
Rake::Task["db:migrate"].dup.invoke
Rake::Task["db:fixtures:load"].dup.invoke
end
end
end
Save the text above into lib/tasks/db_nuke.rake. This task (run it using rake db:nuke) will completely drop your dev and test databases, run all migrations on them, and run rake db:fixtures:load on them. It works on mysql and sqlite. Adapting it for postgres or some other database is left as an exercise for the reader.
Update: I added a dependency on the environment, rather than trying to include the environment files manually. This is the "right" way to do it.
Update: I have added Oracle support.
