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-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
#
# 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
# or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column,
# 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
#
# SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
# end
#
# 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
#
# The Rails package has support for migrations with the <tt>script/generate migration my_new_migration</tt> command and
# with the <tt>rake migrate</tt> command that'll run all the pending migrations. It'll even create the needed schema_info
# table automatically if it's missing.
#
2005-07-04 14:51:02 -04:00
# == Database support
#
# Migrations are currently only supported in MySQL and PostgreSQL.
#
# == 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
#
# def self.down
# # not much we can do to restore deleted data
# 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
#
# And some times you need to do something in SQL not abstracted directly by migrations:
#
# 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
#
# == Using the class after changing table
#
# Some times you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
# to make a call to Base#reset_column_information in order to ensure that the class has the latest column data from
# after the new column was added. Example:
#
# class MakeJoinUnique < ActiveRecord::Migration
# def self.up
# add_column :people, :salary, :integer
# Person.find(:all).each do |p|
# p.salary = SalaryCalculator.compute(p)
# end
# end
# end
2005-07-04 14:51:02 -04:00
class Migration
2005-03-01 09:27:32 -05:00
class << self
def up ( ) end
def down ( ) end
private
def method_missing ( method , * arguments , & block )
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
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 } ) "
migration_class . send ( @direction )
set_schema_version ( version )
end
end
private
def migration_classes
2005-07-09 12:28:15 -04:00
migrations = migration_files . collect do | migration_file |
2005-03-01 09:27:32 -05:00
load ( migration_file )
version , name = migration_version_and_name ( migration_file )
2005-07-09 12:28:15 -04:00
[ version . to_i , migration_class ( name ) ]
2005-03-01 09:27:32 -05:00
end
2005-07-09 12:28:15 -04:00
down? ? migrations . sort . reverse : migrations . sort
2005-03-01 09:27:32 -05:00
end
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