2010-04-05 15:15:08 -04:00
|
|
|
require 'active_support/core_ext/kernel/singleton_class'
|
2010-06-29 15:59:52 -04:00
|
|
|
require 'active_support/core_ext/module/aliasing'
|
2009-06-12 19:01:42 -04:00
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
module ActiveRecord
|
2010-01-16 16:56:20 -05:00
|
|
|
# Exception that can be raised to stop migrations from going backwards.
|
|
|
|
class IrreversibleMigration < ActiveRecordError
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
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
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2008-05-07 01:59:34 -04:00
|
|
|
class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
|
|
|
|
def initialize(name)
|
|
|
|
super("Multiple migrations have the name #{name}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
|
|
|
|
def initialize(version)
|
|
|
|
super("No migration with version number #{version}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-10-17 17:35:19 -04:00
|
|
|
class IllegalMigrationNameError < ActiveRecordError#:nodoc:
|
|
|
|
def initialize(name)
|
|
|
|
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-06-16 13:38:14 -04:00
|
|
|
# = Active Record Migrations
|
2010-08-14 01:13:00 -04:00
|
|
|
#
|
|
|
|
# Migrations can manage the evolution of a schema used by several physical
|
2010-06-15 14:41:30 -04:00
|
|
|
# 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
|
2010-08-14 01:13:00 -04:00
|
|
|
# push that change to other developers and to the production server. With
|
2010-06-15 14:41:30 -04:00
|
|
|
# migrations, you can describe the transformations in self-contained classes
|
2010-08-14 01:13:00 -04:00
|
|
|
# that can be checked into version control systems and executed against
|
2010-06-15 14:41:30 -04:00
|
|
|
# another database that might be one, two, or five versions behind.
|
2005-07-04 14:51:02 -04:00
|
|
|
#
|
|
|
|
# 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
|
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# This migration will add a boolean flag to the accounts table and remove it
|
|
|
|
# if you're backing out of the migration. It shows how all migrations have
|
|
|
|
# two class methods +up+ and +down+ that describes the transformations
|
2010-06-15 14:41:30 -04:00
|
|
|
# required to implement or remove the migration. These methods can consist
|
|
|
|
# of both the migration specific methods like add_column and remove_column,
|
2010-08-14 01:13:00 -04:00
|
|
|
# but may also contain regular Ruby code for generating data needed for the
|
2010-06-15 14:41:30 -04:00
|
|
|
# transformations.
|
2005-07-04 14:51:02 -04:00
|
|
|
#
|
|
|
|
# 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|
|
2007-10-07 23:18:18 -04:00
|
|
|
# t.string :name
|
|
|
|
# t.string :label
|
|
|
|
# t.text :value
|
|
|
|
# t.string :type
|
|
|
|
# t.integer :position
|
2005-07-04 14:51:02 -04:00
|
|
|
# end
|
2005-11-13 22:51:39 -05:00
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# SystemSetting.create :name => "notice",
|
|
|
|
# :label => "Use notice?",
|
2010-06-15 14:41:30 -04:00
|
|
|
# :value => 1
|
2005-07-04 14:51:02 -04:00
|
|
|
# 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
|
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# This migration first adds the system_settings table, then creates the very
|
2010-06-15 14:41:30 -04:00
|
|
|
# first row in it using the Active Record model that relies on the table. It
|
2010-08-14 01:13:00 -04:00
|
|
|
# also uses the more advanced create_table syntax where you can specify a
|
2010-06-15 14:41:30 -04:00
|
|
|
# complete table schema in one block call.
|
2005-07-04 14:51:02 -04:00
|
|
|
#
|
|
|
|
# == Available transformations
|
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>create_table(name, options)</tt> Creates a table called +name+ and
|
2010-06-15 14:41:30 -04:00
|
|
|
# makes the table object available to a block that can then add columns to it,
|
2010-08-14 01:13:00 -04:00
|
|
|
# following the same format as add_column. See example above. The options hash
|
|
|
|
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
|
2010-06-15 14:41:30 -04:00
|
|
|
# table definition.
|
2005-07-04 14:51:02 -04:00
|
|
|
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
|
2010-06-15 14:41:30 -04:00
|
|
|
# to +new_name+.
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
|
2010-06-15 14:41:30 -04:00
|
|
|
# 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:
|
2010-08-14 01:13:00 -04:00
|
|
|
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
|
2010-06-15 14:41:30 -04:00
|
|
|
# <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
|
2010-06-24 00:25:49 -04:00
|
|
|
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
|
2010-08-14 01:13:00 -04:00
|
|
|
# specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
|
|
|
|
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
|
|
|
|
# <tt>{ :limit => 50, :null => false }</tt>) -- see
|
2010-06-15 14:41:30 -04:00
|
|
|
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
|
|
|
|
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
|
|
|
|
# a column but keeps the type and content.
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
|
2010-06-15 14:41:30 -04:00
|
|
|
# the column to a different type using the same parameters as add_column.
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
|
2010-06-15 14:41:30 -04:00
|
|
|
# +column_name+ from the table called +table_name+.
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
|
2010-06-15 14:41:30 -04:00
|
|
|
# with the name of the column. Other options include
|
2010-08-14 01:13:00 -04:00
|
|
|
# <tt>:name</tt> and <tt>:unique</tt> (e.g.
|
2010-06-15 14:41:30 -04:00
|
|
|
# <tt>{ :name => "users_name_index", :unique => true }</tt>).
|
2010-08-14 01:13:00 -04:00
|
|
|
# * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
|
2010-06-15 14:41:30 -04:00
|
|
|
# by +index_name+.
|
2005-07-04 14:51:02 -04:00
|
|
|
#
|
|
|
|
# == Irreversible transformations
|
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# Some transformations are destructive in a manner that cannot be reversed.
|
|
|
|
# Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
|
2010-06-15 14:41:30 -04:00
|
|
|
# exception in their +down+ method.
|
2005-07-04 14:51:02 -04:00
|
|
|
#
|
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.
|
|
|
|
#
|
2009-04-23 12:55:42 -04:00
|
|
|
# To generate a new migration, you can use
|
2010-02-06 11:18:10 -05:00
|
|
|
# rails generate migration MyNewMigration
|
2008-03-26 08:27:52 -04:00
|
|
|
#
|
2005-11-13 22:51:39 -05:00
|
|
|
# where MyNewMigration is the name of your migration. The generator will
|
2010-08-14 01:13:00 -04:00
|
|
|
# create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
|
|
|
|
# in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
|
2010-06-15 14:41:30 -04:00
|
|
|
# UTC formatted date and time that the migration was generated.
|
2008-03-26 08:27:52 -04:00
|
|
|
#
|
2005-11-13 22:51:39 -05:00
|
|
|
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
|
2007-11-07 22:37:16 -05:00
|
|
|
# MyNewMigration.
|
2005-11-13 22:51:39 -05:00
|
|
|
#
|
2008-03-26 08:27:52 -04:00
|
|
|
# There is a special syntactic shortcut to generate migrations that add fields to a table.
|
2010-06-15 14:41:30 -04:00
|
|
|
#
|
2010-02-06 11:18:10 -05:00
|
|
|
# rails generate migration add_fieldname_to_tablename fieldname:string
|
2008-03-26 08:27:52 -04:00
|
|
|
#
|
2009-07-25 11:03:58 -04:00
|
|
|
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
|
2008-03-26 08:27:52 -04:00
|
|
|
# class AddFieldnameToTablename < ActiveRecord::Migration
|
|
|
|
# def self.up
|
|
|
|
# add_column :tablenames, :fieldname, :string
|
|
|
|
# end
|
2009-04-23 12:55:42 -04:00
|
|
|
#
|
2008-03-26 08:27:52 -04:00
|
|
|
# def self.down
|
|
|
|
# remove_column :tablenames, :fieldname
|
|
|
|
# end
|
|
|
|
# end
|
2009-04-23 12:55:42 -04:00
|
|
|
#
|
2005-11-13 22:51:39 -05:00
|
|
|
# To run migrations against the currently configured database, use
|
2007-09-22 14:35:41 -04:00
|
|
|
# <tt>rake db:migrate</tt>. This will update the database by running all of the
|
2008-04-09 12:20:15 -04:00
|
|
|
# pending migrations, creating the <tt>schema_migrations</tt> table
|
2009-04-23 12:55:42 -04:00
|
|
|
# (see "About the schema_migrations table" section below) if missing. It will also
|
2008-12-06 21:27:53 -05:00
|
|
|
# invoke the db:schema:dump task, which will update your db/schema.rb file
|
|
|
|
# to match the structure of your database.
|
2005-11-13 22:51:39 -05:00
|
|
|
#
|
|
|
|
# To roll the database back to a previous migration version, use
|
2007-09-22 14:35:41 -04:00
|
|
|
# <tt>rake db: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
|
2007-11-11 16:31:59 -05:00
|
|
|
# <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
|
2005-11-13 22:51:39 -05:00
|
|
|
# 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,
|
2006-03-17 22:02:32 -05:00
|
|
|
# SQL Server, Sybase, 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
|
2007-11-11 16:31:59 -05:00
|
|
|
# raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
|
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
|
|
|
#
|
2010-08-14 01:13:00 -04:00
|
|
|
# Sometimes 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
|
|
|
|
# <tt>Base#reset_column_information</tt> in order to ensure that the model has the
|
2010-06-15 14:41:30 -04:00
|
|
|
# latest column data from after the new column was added. Example:
|
2005-09-08 13:49:31 -04:00
|
|
|
#
|
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|
|
2006-10-09 18:05:50 -04:00
|
|
|
# p.update_attribute :salary, SalaryCalculator.compute(p)
|
2005-09-08 13:49:31 -04:00
|
|
|
# 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.
|
|
|
|
#
|
2008-05-25 07:29:00 -04:00
|
|
|
# You can also insert your own messages and benchmarks by using the +say_with_time+
|
2006-03-04 13:46:51 -05:00
|
|
|
# method:
|
|
|
|
#
|
|
|
|
# def self.up
|
|
|
|
# ...
|
|
|
|
# say_with_time "Updating salaries..." do
|
|
|
|
# Person.find(:all).each do |p|
|
2006-10-09 18:05:50 -04:00
|
|
|
# p.update_attribute :salary, SalaryCalculator.compute(p)
|
2006-03-04 13:46:51 -05:00
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# ...
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# The phrase "Updating salaries..." would then be printed, along with the
|
|
|
|
# benchmark for the block when the block completes.
|
2008-04-09 12:20:15 -04:00
|
|
|
#
|
|
|
|
# == About the schema_migrations table
|
|
|
|
#
|
|
|
|
# Rails versions 2.0 and prior used to create a table called
|
|
|
|
# <tt>schema_info</tt> when using migrations. This table contained the
|
|
|
|
# version of the schema as of the last applied migration.
|
|
|
|
#
|
|
|
|
# Starting with Rails 2.1, the <tt>schema_info</tt> table is
|
|
|
|
# (automatically) replaced by the <tt>schema_migrations</tt> table, which
|
|
|
|
# contains the version numbers of all the migrations applied.
|
|
|
|
#
|
|
|
|
# As a result, it is now possible to add migration files that are numbered
|
|
|
|
# lower than the current schema version: when migrating up, those
|
|
|
|
# never-applied "interleaved" migrations will be automatically applied, and
|
|
|
|
# when migrating down, never-applied "interleaved" migrations will be skipped.
|
2009-04-23 12:55:42 -04:00
|
|
|
#
|
2008-07-16 21:50:29 -04:00
|
|
|
# == Timestamped Migrations
|
|
|
|
#
|
|
|
|
# By default, Rails generates migrations that look like:
|
|
|
|
#
|
|
|
|
# 20080717013526_your_migration_name.rb
|
|
|
|
#
|
|
|
|
# The prefix is a generation timestamp (in UTC).
|
|
|
|
#
|
|
|
|
# If you'd prefer to use numeric prefixes, you can turn timestamped migrations
|
|
|
|
# off by setting:
|
|
|
|
#
|
|
|
|
# config.active_record.timestamped_migrations = false
|
2009-04-23 12:55:42 -04:00
|
|
|
#
|
2010-07-09 09:58:58 -04:00
|
|
|
# In application.rb.
|
2008-07-16 21:50:29 -04:00
|
|
|
#
|
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-11-19 06:09:31 -05:00
|
|
|
def up_with_benchmarks #:nodoc:
|
2006-03-04 13:46:51 -05:00
|
|
|
migrate(:up)
|
|
|
|
end
|
|
|
|
|
2006-11-19 06:09:31 -05:00
|
|
|
def down_with_benchmarks #:nodoc:
|
2006-03-04 13:46:51 -05:00
|
|
|
migrate(:down)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Execute this migration in the named direction
|
|
|
|
def migrate(direction)
|
|
|
|
return unless respond_to?(direction)
|
|
|
|
|
|
|
|
case direction
|
2006-03-18 11:28:40 -05:00
|
|
|
when :up then announce "migrating"
|
|
|
|
when :down then announce "reverting"
|
2006-03-04 13:46:51 -05:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2006-03-04 13:46:51 -05:00
|
|
|
result = nil
|
2006-11-19 06:09:31 -05:00
|
|
|
time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
|
2006-03-04 13:46:51 -05:00
|
|
|
|
|
|
|
case direction
|
2006-03-18 11:28:40 -05:00
|
|
|
when :up then announce "migrated (%.4fs)" % time.real; write
|
|
|
|
when :down then announce "reverted (%.4fs)" % time.real; write
|
2006-03-04 13:46:51 -05:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2006-03-04 13:46:51 -05:00
|
|
|
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:
|
2007-12-22 06:26:03 -05:00
|
|
|
return if defined?(@ignore_new_methods) && @ignore_new_methods
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2006-03-04 13:46:51 -05:00
|
|
|
begin
|
|
|
|
@ignore_new_methods = true
|
2006-03-18 11:28:40 -05:00
|
|
|
|
2006-03-04 13:46:51 -05:00
|
|
|
case sym
|
2006-03-18 11:28:40 -05:00
|
|
|
when :up, :down
|
2010-02-25 12:32:29 -05:00
|
|
|
singleton_class.send(:alias_method_chain, sym, "benchmarks")
|
2006-03-04 13:46:51 -05:00
|
|
|
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)
|
2010-03-14 20:43:59 -04:00
|
|
|
version = defined?(@version) ? @version : nil
|
|
|
|
|
|
|
|
text = "#{version} #{name}: #{message}"
|
2006-08-04 21:39:57 -04:00
|
|
|
length = [0, 75 - text.length].max
|
|
|
|
write "== %s %s" % [text, "=" * length]
|
2006-03-04 13:46:51 -05:00
|
|
|
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
|
2007-06-05 04:21:55 -04:00
|
|
|
say("#{result} rows", :subitem) if result.is_a?(Integer)
|
2006-03-04 13:46:51 -05:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2006-03-09 13:38:39 -05:00
|
|
|
def suppress_messages
|
2007-05-25 22:36:55 -04:00
|
|
|
save, self.verbose = verbose, false
|
2006-03-09 13:38:39 -05:00
|
|
|
yield
|
|
|
|
ensure
|
|
|
|
self.verbose = save
|
|
|
|
end
|
|
|
|
|
2010-01-07 16:30:51 -05:00
|
|
|
def connection
|
|
|
|
ActiveRecord::Base.connection
|
|
|
|
end
|
|
|
|
|
2006-03-04 13:46:51 -05:00
|
|
|
def method_missing(method, *arguments, &block)
|
2010-08-13 18:59:15 -04:00
|
|
|
arg_list = arguments.map{ |a| a.inspect } * ', '
|
2007-05-25 22:36:55 -04:00
|
|
|
|
|
|
|
say_with_time "#{method}(#{arg_list})" do
|
|
|
|
unless arguments.empty? || method == :execute
|
|
|
|
arguments[0] = Migrator.proper_table_name(arguments.first)
|
|
|
|
end
|
2010-01-07 16:30:51 -05:00
|
|
|
connection.send(method, *arguments, &block)
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2006-03-04 13:46:51 -05:00
|
|
|
end
|
2010-07-26 08:06:14 -04:00
|
|
|
|
|
|
|
def copy(destination, sources)
|
|
|
|
copied = []
|
|
|
|
|
|
|
|
sources.each do |scope, path|
|
|
|
|
destination_migrations = ActiveRecord::Migrator.migrations(destination)
|
|
|
|
source_migrations = ActiveRecord::Migrator.migrations(path)
|
|
|
|
last = destination_migrations.last
|
|
|
|
|
|
|
|
source_migrations.each do |migration|
|
|
|
|
next if destination_migrations.any? { |m| m.name == migration.name && m.scope == scope.to_s }
|
|
|
|
|
2010-07-31 08:43:41 -04:00
|
|
|
migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
|
2010-07-26 08:06:14 -04:00
|
|
|
last = migration
|
|
|
|
|
|
|
|
new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
|
|
|
|
FileUtils.cp(migration.filename, new_path)
|
|
|
|
copied << new_path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
copied
|
|
|
|
end
|
|
|
|
|
|
|
|
def next_migration_number(number)
|
|
|
|
if ActiveRecord::Base.timestamped_migrations
|
|
|
|
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
|
|
|
|
else
|
|
|
|
"%.3d" % number
|
|
|
|
end
|
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-08-02 01:44:02 -04:00
|
|
|
# MigrationProxy is used to defer loading of the actual migration classes
|
|
|
|
# until they are needed
|
2010-10-03 19:32:27 -04:00
|
|
|
class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
|
2008-08-02 01:44:02 -04:00
|
|
|
|
2010-10-03 19:32:27 -04:00
|
|
|
def initialize(name, version, filename, scope)
|
|
|
|
super
|
|
|
|
@migration = nil
|
|
|
|
end
|
2008-08-02 01:44:02 -04:00
|
|
|
|
|
|
|
delegate :migrate, :announce, :write, :to=>:migration
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def migration
|
|
|
|
@migration ||= load_migration
|
|
|
|
end
|
|
|
|
|
|
|
|
def load_migration
|
2010-03-23 21:20:28 -04:00
|
|
|
require(File.expand_path(filename))
|
2008-08-02 01:44:02 -04:00
|
|
|
name.constantize
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2005-03-22 08:09:44 -05:00
|
|
|
class Migrator#:nodoc:
|
2005-03-01 09:27:32 -05:00
|
|
|
class << self
|
2010-07-25 13:04:32 -04:00
|
|
|
attr_writer :migrations_path
|
|
|
|
|
2005-07-09 11:46:29 -04:00
|
|
|
def migrate(migrations_path, target_version = nil)
|
|
|
|
case
|
2010-04-20 11:14:00 -04:00
|
|
|
when target_version.nil?
|
|
|
|
up(migrations_path, target_version)
|
|
|
|
when current_version == 0 && target_version == 0
|
|
|
|
when current_version > target_version
|
|
|
|
down(migrations_path, target_version)
|
|
|
|
else
|
|
|
|
up(migrations_path, target_version)
|
2005-07-09 11:46:29 -04:00
|
|
|
end
|
|
|
|
end
|
2008-04-09 12:20:15 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
def rollback(migrations_path, steps=1)
|
2009-08-08 15:47:14 -04:00
|
|
|
move(:down, migrations_path, steps)
|
2008-03-28 17:21:01 -04:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2009-08-08 12:39:31 -04:00
|
|
|
def forward(migrations_path, steps=1)
|
2009-08-08 15:47:14 -04:00
|
|
|
move(:up, migrations_path, steps)
|
2009-08-08 12:39:31 -04:00
|
|
|
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
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
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
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
def run(direction, migrations_path, target_version)
|
2008-03-28 18:14:04 -04:00
|
|
|
self.new(direction, migrations_path, target_version).run
|
2008-03-28 17:21:01 -04:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2008-04-09 12:20:15 -04:00
|
|
|
def schema_migrations_table_name
|
|
|
|
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
|
2005-09-26 17:30:12 -04:00
|
|
|
end
|
|
|
|
|
2008-08-26 05:57:33 -04:00
|
|
|
def get_all_versions
|
2009-06-02 11:27:26 -04:00
|
|
|
table = Arel::Table.new(schema_migrations_table_name)
|
2010-08-13 18:59:15 -04:00
|
|
|
Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
|
2008-08-26 05:57:33 -04:00
|
|
|
end
|
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
def current_version
|
2008-08-26 02:14:12 -04:00
|
|
|
sm_table = schema_migrations_table_name
|
|
|
|
if Base.connection.table_exists?(sm_table)
|
2008-08-26 05:57:33 -04:00
|
|
|
get_all_versions.max || 0
|
2008-08-26 02:14:12 -04:00
|
|
|
else
|
|
|
|
0
|
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2005-09-26 17:30:12 -04:00
|
|
|
|
|
|
|
def proper_table_name(name)
|
2008-05-25 07:29:00 -04:00
|
|
|
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
|
2005-09-26 17:30:12 -04:00
|
|
|
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
|
|
|
|
end
|
2009-08-08 15:47:14 -04:00
|
|
|
|
2010-07-25 13:04:32 -04:00
|
|
|
def migrations_path
|
|
|
|
@migrations_path ||= 'db/migrate'
|
|
|
|
end
|
|
|
|
|
2010-07-26 08:06:14 -04:00
|
|
|
def migrations(path)
|
|
|
|
files = Dir["#{path}/[0-9]*_*.rb"]
|
|
|
|
|
|
|
|
migrations = files.inject([]) do |klasses, file|
|
|
|
|
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
|
2010-10-03 19:36:43 -04:00
|
|
|
name = name.camelize
|
2010-07-26 08:06:14 -04:00
|
|
|
|
|
|
|
raise IllegalMigrationNameError.new(file) unless version
|
|
|
|
version = version.to_i
|
|
|
|
|
|
|
|
if klasses.detect { |m| m.version == version }
|
|
|
|
raise DuplicateMigrationVersionError.new(version)
|
|
|
|
end
|
|
|
|
|
2010-10-03 19:36:43 -04:00
|
|
|
if klasses.detect { |m| m.name == name && m.scope == scope }
|
|
|
|
raise DuplicateMigrationNameError.new(name)
|
2010-07-26 08:06:14 -04:00
|
|
|
end
|
|
|
|
|
2010-10-03 19:36:43 -04:00
|
|
|
migration = MigrationProxy.new(name, version, file, scope)
|
2010-07-26 08:06:14 -04:00
|
|
|
klasses << migration
|
|
|
|
end
|
|
|
|
|
|
|
|
migrations.sort_by(&:version)
|
|
|
|
end
|
|
|
|
|
2009-08-08 15:47:14 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def move(direction, migrations_path, steps)
|
|
|
|
migrator = self.new(direction, migrations_path)
|
|
|
|
start_index = migrator.migrations.index(migrator.current_migration)
|
|
|
|
|
|
|
|
if start_index
|
|
|
|
finish = migrator.migrations[start_index + steps]
|
|
|
|
version = finish ? finish.version : 0
|
|
|
|
send(direction, migrations_path, version)
|
|
|
|
end
|
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
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?
|
2008-04-09 12:20:15 -04:00
|
|
|
Base.connection.initialize_schema_migrations_table
|
2009-04-23 12:55:42 -04:00
|
|
|
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def current_version
|
2008-08-25 21:55:57 -04:00
|
|
|
migrated.last || 0
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
def current_migration
|
|
|
|
migrations.detect { |m| m.version == current_version }
|
|
|
|
end
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
def run
|
|
|
|
target = migrations.detect { |m| m.version == @target_version }
|
|
|
|
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
2008-06-13 10:14:07 -04:00
|
|
|
unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
|
|
|
|
target.migrate(@direction)
|
|
|
|
record_version_state_after_migrating(target.version)
|
|
|
|
end
|
2008-03-28 17:21:01 -04:00
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
|
|
|
|
def migrate
|
2008-03-28 17:21:01 -04:00
|
|
|
current = migrations.detect { |m| m.version == current_version }
|
|
|
|
target = migrations.detect { |m| m.version == @target_version }
|
2008-04-09 12:20:15 -04:00
|
|
|
|
2010-10-03 19:13:45 -04:00
|
|
|
if target.nil? && @target_version && @target_version > 0
|
2008-03-28 17:21:01 -04:00
|
|
|
raise UnknownMigrationVersionError.new(@target_version)
|
|
|
|
end
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-04-09 12:20:15 -04:00
|
|
|
start = up? ? 0 : (migrations.index(current) || 0)
|
|
|
|
finish = migrations.index(target) || migrations.size - 1
|
2008-03-28 17:21:01 -04:00
|
|
|
runnable = migrations[start..finish]
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
# skip the last migration if we're headed down, but not ALL the way down
|
2010-10-03 19:13:45 -04:00
|
|
|
runnable.pop if down? && target
|
2009-04-23 12:55:42 -04:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
runnable.each do |migration|
|
2009-10-27 19:30:24 -04:00
|
|
|
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
|
2008-04-09 12:20:15 -04:00
|
|
|
|
2010-10-03 19:23:07 -04:00
|
|
|
seen = migrated.include?(migration.version.to_i)
|
|
|
|
|
2008-04-09 12:20:15 -04:00
|
|
|
# On our way up, we skip migrating the ones we've already migrated
|
2010-10-03 19:23:07 -04:00
|
|
|
next if up? && seen
|
2008-04-09 12:20:15 -04:00
|
|
|
|
2008-08-22 16:53:31 -04:00
|
|
|
# On our way down, we skip reverting the ones we've never migrated
|
2010-10-03 19:23:07 -04:00
|
|
|
if down? && !seen
|
2008-04-09 12:20:15 -04:00
|
|
|
migration.announce 'never migrated, skipping'; migration.write
|
2008-08-22 16:53:31 -04:00
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
ddl_transaction do
|
|
|
|
migration.migrate(@direction)
|
|
|
|
record_version_state_after_migrating(migration.version)
|
|
|
|
end
|
|
|
|
rescue => e
|
|
|
|
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
|
|
|
|
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
|
2008-04-09 12:20:15 -04:00
|
|
|
end
|
2008-03-28 17:21:01 -04:00
|
|
|
end
|
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
|
2008-03-28 17:21:01 -04:00
|
|
|
def migrations
|
|
|
|
@migrations ||= begin
|
2010-07-26 08:06:14 -04:00
|
|
|
migrations = self.class.migrations(@migrations_path)
|
2008-03-28 17:21:01 -04:00
|
|
|
down? ? migrations.reverse : migrations
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-12-06 13:57:19 -05:00
|
|
|
def pending_migrations
|
2008-04-09 12:20:15 -04:00
|
|
|
already_migrated = migrated
|
|
|
|
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def migrated
|
2008-08-26 05:57:33 -04:00
|
|
|
@migrated_versions ||= self.class.get_all_versions
|
2007-12-06 13:57:19 -05:00
|
|
|
end
|
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
private
|
2008-04-09 12:20:15 -04:00
|
|
|
def record_version_state_after_migrating(version)
|
2009-06-02 11:27:26 -04:00
|
|
|
table = Arel::Table.new(self.class.schema_migrations_table_name)
|
2008-04-09 12:20:15 -04:00
|
|
|
|
2008-08-25 21:55:57 -04:00
|
|
|
@migrated_versions ||= []
|
2008-03-28 17:21:01 -04:00
|
|
|
if down?
|
2009-05-06 13:16:03 -04:00
|
|
|
@migrated_versions.delete(version)
|
|
|
|
table.where(table["version"].eq(version.to_s)).delete
|
2008-04-09 12:20:15 -04:00
|
|
|
else
|
2009-05-06 13:16:03 -04:00
|
|
|
@migrated_versions.push(version).sort!
|
|
|
|
table.insert table["version"] => version.to_s
|
2005-09-21 09:27:45 -04:00
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
def up?
|
|
|
|
@direction == :up
|
|
|
|
end
|
2007-05-25 22:36:55 -04:00
|
|
|
|
2005-03-01 09:27:32 -05:00
|
|
|
def down?
|
|
|
|
@direction == :down
|
|
|
|
end
|
2008-08-22 16:53:31 -04:00
|
|
|
|
|
|
|
# Wrap the migration in a transaction only if supported by the adapter.
|
|
|
|
def ddl_transaction(&block)
|
|
|
|
if Base.connection.supports_ddl_transactions?
|
|
|
|
Base.transaction { block.call }
|
|
|
|
else
|
|
|
|
block.call
|
|
|
|
end
|
|
|
|
end
|
2005-03-01 09:27:32 -05:00
|
|
|
end
|
2005-06-07 13:00:43 -04:00
|
|
|
end
|