2005-03-01 09:27:32 -05:00
module ActiveRecord
2005-03-22 08:09:44 -05:00
class IrreversibleMigration < ActiveRecordError #:nodoc:
2005-03-01 09:27:32 -05:00
end
2005-10-31 10:43:02 -05:00
class DuplicateMigrationVersionError < ActiveRecordError #:nodoc:
def initialize ( version )
super ( " Multiple migrations have the version number #{ version } " )
end
end
2005-07-04 14:51:02 -04:00
# Migrations can manage the evolution of a schema used by several physical databases. It's a solution
# to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
# push that change to other developers and to the production server. With migrations, you can describe the transformations
# in self-contained classes that can be checked into version control systems and executed against another database that
# might be one, two, or five versions behind.
#
# Example of a simple migration:
#
# class AddSsl < ActiveRecord::Migration
# def self.up
# add_column :accounts, :ssl_enabled, :boolean, :default => 1
# end
2005-11-13 22:51:39 -05:00
#
2005-07-04 14:51:02 -04:00
# def self.down
# remove_column :accounts, :ssl_enabled
# end
# end
#
# This migration will add a boolean flag to the accounts table and remove it again, if you're backing out of the migration.
# It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
2005-11-13 22:51:39 -05:00
# or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column,
2005-07-04 14:51:02 -04:00
# but may also contain regular Ruby code for generating data needed for the transformations.
#
# Example of a more complex migration that also needs to initialize data:
#
# class AddSystemSettings < ActiveRecord::Migration
# def self.up
# create_table :system_settings do |t|
# t.column :name, :string
# t.column :label, :string
# t.column :value, :text
# t.column :type, :string
# t.column :position, :integer
# end
2005-11-13 22:51:39 -05:00
#
2005-07-04 14:51:02 -04:00
# SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
# end
2005-11-13 22:51:39 -05:00
#
2005-07-04 14:51:02 -04:00
# def self.down
# drop_table :system_settings
# end
# end
#
# This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
# that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
# in one block call.
#
# == Available transformations
#
2005-07-05 03:19:20 -04:00
# * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
# that can then add columns to it, following the same format as add_column. See example above. The options hash is for
2005-07-04 14:51:02 -04:00
# fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
2005-07-05 03:19:20 -04:00
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
2005-07-04 14:51:02 -04:00
# named +column_name+ specified to be one of the following types:
# :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
# by passing an +options+ hash like { :default => 11 }.
2005-07-05 03:19:20 -04:00
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
# parameters as add_column.
2005-07-04 14:51:02 -04:00
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
2005-09-02 06:07:14 -04:00
# * <tt>add_index(table_name, column_name, index_type)</tt>: Add a new index with the name of the column on the column. Specify an optional index_type (e.g. UNIQUE).
2005-07-05 03:19:20 -04:00
# * <tt>remove_index(table_name, column_name)</tt>: Remove the index called the same as the column.
2005-07-04 14:51:02 -04:00
#
# == Irreversible transformations
#
# Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
# an <tt>IrreversibleMigration</tt> exception in their +down+ method.
#
2005-07-05 03:19:20 -04:00
# == Running migrations from within Rails
#
2005-11-13 22:51:39 -05:00
# The Rails package has several tools to help create and apply migrations.
#
# To generate a new migration, use <tt>script/generate migration MyNewMigration</tt>
# where MyNewMigration is the name of your migration. The generator will
# create a file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
# directory, where <tt>nnn</tt> is the next largest migration number.
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
# n MyNewMigration.
#
# To run migrations against the currently configured database, use
# <tt>rake migrate</tt>. This will update the database by running all of the
# pending migrations, creating the <tt>schema_info</tt> table if missing.
#
# To roll the database back to a previous migration version, use
2006-01-08 23:43:27 -05:00
# <tt>rake migrate VERSION=X</tt> where <tt>X</tt> is the version to which
2005-11-13 22:51:39 -05:00
# you wish to downgrade. If any of the migrations throw an
# <tt>IrreversibleMigration</tt> exception, that step will fail and you'll
# have some manual work to do.
2005-07-05 03:19:20 -04:00
#
2005-07-04 14:51:02 -04:00
# == Database support
#
2005-11-13 22:51:39 -05:00
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
# SQL Server, and Oracle (all supported databases except DB2).
2005-07-04 14:51:02 -04:00
#
# == More examples
#
# Not all migrations change the schema. Some just fix the data:
#
# class RemoveEmptyTags < ActiveRecord::Migration
# def self.up
# Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
# end
2005-11-13 22:51:39 -05:00
#
2005-07-04 14:51:02 -04:00
# def self.down
# # not much we can do to restore deleted data
2005-10-26 09:29:11 -04:00
# raise IrreversibleMigration
2005-07-04 14:51:02 -04:00
# end
# end
#
# Others remove columns when they migrate up instead of down:
#
# class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
# def self.up
# remove_column :items, :incomplete_items_count
# remove_column :items, :completed_items_count
# end
#
# def self.down
# add_column :items, :incomplete_items_count
# add_column :items, :completed_items_count
# end
# end
2005-07-09 11:46:29 -04:00
#
2005-10-26 09:05:48 -04:00
# And sometimes you need to do something in SQL not abstracted directly by migrations:
2005-07-09 11:46:29 -04:00
#
# class MakeJoinUnique < ActiveRecord::Migration
# def self.up
# execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
# end
#
# def self.down
# execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
# end
# end
2005-09-08 13:49:31 -04:00
#
2005-11-13 22:51:39 -05:00
# == Using a model after changing its table
2005-09-08 13:49:31 -04:00
#
2005-10-26 09:05:48 -04:00
# Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
2005-11-13 22:51:39 -05:00
# to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
2005-09-08 13:49:31 -04:00
# after the new column was added. Example:
#
2005-11-13 22:51:39 -05:00
# class AddPeopleSalary < ActiveRecord::Migration
2005-09-08 13:49:31 -04:00
# def self.up
# add_column :people, :salary, :integer
2005-11-13 22:51:39 -05:00
# Person.reset_column_information
2005-09-08 13:49:31 -04:00
# Person.find(:all).each do |p|
# p.salary = SalaryCalculator.compute(p)
# end
# end
2005-11-13 22:51:39 -05:00
# end
2006-03-04 13:46:51 -05:00
#
# == Controlling verbosity
#
# By default, migrations will describe the actions they are taking, writing
# them to the console as they happen, along with benchmarks describing how
# long each step took.
#
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
#
# You can also insert your own messages and benchmarks by using the #say_with_time
# method:
#
# def self.up
# ...
# say_with_time "Updating salaries..." do
# Person.find(:all).each do |p|
# p.salary = SalaryCalculator.compute(p)
# end
# end
# ...
# end
#
# The phrase "Updating salaries..." would then be printed, along with the
# benchmark for the block when the block completes.
2005-07-04 14:51:02 -04:00
class Migration
2006-03-04 13:46:51 -05:00
@@verbose = true
cattr_accessor :verbose
2005-03-01 09:27:32 -05:00
class << self
2006-03-04 13:46:51 -05:00
def up_using_benchmarks #:nodoc:
migrate ( :up )
end
def down_using_benchmarks #:nodoc:
migrate ( :down )
end
# Execute this migration in the named direction
def migrate ( direction )
return unless respond_to? ( direction )
case direction
when :up then announce " migrating "
when :down then announce " reverting "
end
result = nil
time = Benchmark . measure { result = send ( " real_ #{ direction } " ) }
case direction
when :up then announce " migrated (%.4fs) " % time . real ; write
when :down then announce " reverted (%.4fs) " % time . real ; write
end
result
end
# Because the method added may do an alias_method, it can be invoked
# recursively. We use @ignore_new_methods as a guard to indicate whether
# it is safe for the call to proceed.
def singleton_method_added ( sym ) #:nodoc:
return if @ignore_new_methods
begin
@ignore_new_methods = true
case sym
when :up , :down
klass = ( class << self ; self ; end )
klass . send ( :alias_method , " real_ #{ sym } " , sym )
klass . send ( :alias_method , sym , " #{ sym } _using_benchmarks " )
end
ensure
@ignore_new_methods = false
end
end
2005-03-01 09:27:32 -05:00
2006-03-04 13:46:51 -05:00
def write ( text = " " )
puts ( text ) if verbose
end
def announce ( message )
text = " #{ name } : #{ message } "
write " == %s %s " % [ text , " = " * ( 75 - text . length ) ]
end
def say ( message , subitem = false )
write " #{ subitem ? " -> " : " -- " } #{ message } "
end
def say_with_time ( message )
say ( message )
result = nil
time = Benchmark . measure { result = yield }
say " %.4fs " % time . real , :subitem
result
end
def method_missing ( method , * arguments , & block )
say_with_time " #{ method } ( #{ arguments . map { | a | a . inspect } . join ( " , " ) } ) " do
2005-09-26 17:30:12 -04:00
arguments [ 0 ] = Migrator . proper_table_name ( arguments . first ) unless arguments . empty?
2005-03-01 09:27:32 -05:00
ActiveRecord :: Base . connection . send ( method , * arguments , & block )
end
2006-03-04 13:46:51 -05:00
end
2005-03-01 09:27:32 -05:00
end
end
2005-03-22 08:09:44 -05:00
class Migrator #:nodoc:
2005-03-01 09:27:32 -05:00
class << self
2005-07-09 11:46:29 -04:00
def migrate ( migrations_path , target_version = nil )
2005-09-02 10:20:20 -04:00
Base . connection . initialize_schema_information
2005-07-09 11:46:29 -04:00
case
when target_version . nil? , current_version < target_version
up ( migrations_path , target_version )
when current_version > target_version
down ( migrations_path , target_version )
when current_version == target_version
return # You're on the right version
end
end
2005-03-01 09:27:32 -05:00
def up ( migrations_path , target_version = nil )
2005-07-04 14:51:02 -04:00
self . new ( :up , migrations_path , target_version ) . migrate
2005-03-01 09:27:32 -05:00
end
def down ( migrations_path , target_version = nil )
2005-07-04 14:51:02 -04:00
self . new ( :down , migrations_path , target_version ) . migrate
2005-03-01 09:27:32 -05:00
end
2005-09-26 17:30:12 -04:00
def schema_info_table_name
Base . table_name_prefix + " schema_info " + Base . table_name_suffix
end
2005-03-01 09:27:32 -05:00
def current_version
2005-09-26 17:30:12 -04:00
( Base . connection . select_one ( " SELECT version FROM #{ schema_info_table_name } " ) || { " version " = > 0 } ) [ " version " ] . to_i
2005-03-01 09:27:32 -05:00
end
2005-09-26 17:30:12 -04:00
def proper_table_name ( name )
# Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
name . table_name rescue " #{ ActiveRecord :: Base . table_name_prefix } #{ name } #{ ActiveRecord :: Base . table_name_suffix } "
end
2005-03-01 09:27:32 -05:00
end
def initialize ( direction , migrations_path , target_version = nil )
2005-07-04 14:51:02 -04:00
raise StandardError . new ( " This database does not yet support migrations " ) unless Base . connection . supports_migrations?
2005-03-01 09:27:32 -05:00
@direction , @migrations_path , @target_version = direction , migrations_path , target_version
2005-03-01 09:50:48 -05:00
Base . connection . initialize_schema_information
2005-03-01 09:27:32 -05:00
end
def current_version
self . class . current_version
end
def migrate
2005-07-09 12:28:15 -04:00
migration_classes . each do | ( version , migration_class ) |
2005-03-01 09:27:32 -05:00
Base . logger . info ( " Reached target version: #{ @target_version } " ) and break if reached_target_version? ( version )
next if irrelevant_migration? ( version )
Base . logger . info " Migrating to #{ migration_class } ( #{ version } ) "
2006-03-04 13:46:51 -05:00
migration_class . migrate ( @direction )
2005-03-01 09:27:32 -05:00
set_schema_version ( version )
end
end
private
def migration_classes
2005-10-31 10:43:02 -05:00
migrations = migration_files . inject ( [ ] ) do | migrations , migration_file |
2005-03-01 09:27:32 -05:00
load ( migration_file )
version , name = migration_version_and_name ( migration_file )
2005-10-31 10:43:02 -05:00
assert_unique_migration_version ( migrations , version . to_i )
migrations << [ version . to_i , migration_class ( name ) ]
2005-03-01 09:27:32 -05:00
end
2005-10-31 10:43:02 -05:00
2005-07-09 12:28:15 -04:00
down? ? migrations . sort . reverse : migrations . sort
2005-03-01 09:27:32 -05:00
end
2005-10-31 10:43:02 -05:00
def assert_unique_migration_version ( migrations , version )
if ! migrations . empty? && migrations . transpose . first . include? ( version )
raise DuplicateMigrationVersionError . new ( version )
end
end
2005-03-01 09:27:32 -05:00
def migration_files
2005-09-21 09:27:45 -04:00
files = Dir [ " #{ @migrations_path } /[0-9]*_*.rb " ] . sort_by do | f |
migration_version_and_name ( f ) . first . to_i
end
2005-03-01 09:27:32 -05:00
down? ? files . reverse : files
end
2005-09-21 09:27:45 -04:00
2005-03-01 09:27:32 -05:00
def migration_class ( migration_name )
migration_name . camelize . constantize
end
def migration_version_and_name ( migration_file )
return * migration_file . scan ( / ([0-9]+)_([_a-z0-9]*).rb / ) . first
end
def set_schema_version ( version )
2005-09-26 17:30:12 -04:00
Base . connection . update ( " UPDATE #{ self . class . schema_info_table_name } SET version = #{ down? ? version . to_i - 1 : version . to_i } " )
2005-03-01 09:27:32 -05:00
end
def up?
@direction == :up
end
def down?
@direction == :down
end
def reached_target_version? ( version )
( up? && version . to_i - 1 == @target_version ) || ( down? && version . to_i == @target_version )
end
def irrelevant_migration? ( version )
( up? && version . to_i < = current_version ) || ( down? && version . to_i > current_version )
end
end
2005-06-07 13:00:43 -04:00
end