Migrationは物理的なデータベースによって使われるスキーマの変更を
管理するためのものです。新しい機能のために、
データベースにフィールドを追加する必要がある時
他の開発者にどのように変更を伝えるか、
商用サーバにそれをどう適用すればよいかを
伝えるための共通の問題に対するソリューションです。
Migrationを使う事で、変更をクラスとして記述する事ができ、
バージョン管理システムで管理し、他のデータベースに対して
1、2または5つ以上のバージョンを適用することができます。
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
このMigrationはaccountテーブルにbooleanのフラグを追加し、
Migrationを戻すとそのフラグを削除するものです。
これは全てのMigrationが、移行に必要な実装/削除に必要な変更を記述するため、
2つのクラスメソッドupとdownを持つことを示しています。
これらのメソッドは、migrationのメソッドであるadd_columnやremove_columnだけでなく、
変更に必要なデータを生成するための通常のRubyコードを含む事ができます。
データの初期化が必要な場合のもう少し複雑なmigrationの例は次のようになります:
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
このMigrationは最初にsystem_settingテーブルを追加し、
ActiveRecordモデルを利用して最初の行を作成しています。
また、これは完全なテーブルスキーマをブロックコールで作成する、
create_tableのより高度な文法を使用しています。
使用可能な変換
create_table(name, options)
:nameというテーブルを作成し、
ブロックでadd_columnと同じフォーマットで列を作成できるようにします。
上記の例を参照してください。このoptionsハッシュはDEFAULT CHARSET="UTF-8"のような
フラグメントをcreate tableの定義に追加するためのものです。
drop_table(name)
:nameテーブルをdropします。
rename_table(old_name, new_name)
:old_nameというテーブルを
new_nameにリネームします。
add_column(table_name, column_name, type, options)
:
table_nameというテーブルにcolumn_nameという名前の新しい列を追加します。
typeは以下のタイプです:
:string、:text、:integer、:float、:decimal、:datetime、:timestamp、:time、:date、:binary、
:boolean。
デフォルト地はoptionsハッシュに{:default => 11}のようにして追加できます。
ほかにも:limitや:null(例えば{ :limit => 50, :null => false}
)を追加できます。
詳しくはActiveRecord::ConnectionAdapters::TableDefinition#columnを参照してください。
rename_column(table_name, column_name, new_column_name)
:
列をリネームします。タイプと内容は保持します。
change_column(table_name, column_name, type, options)
:
add_columnと同じパラメータで、列を別のタイプに変更します。
remove_column(table_name, column_name)
:table_nameという名前の
テーブルからcolumn_nameという列を削除します。
add_index(table_name, column_names, index_type, index_name)
:
新しいインデックスを列名または(もし指定されていれば)index_nameをつかって作成する。
index_type(例えばUNIQUE)も指定できる。
remove_index(table_name, index_name)
:
index_nameで指定したindexを削除する。
復元不可能な変更
いくつかの変更は戻す事のできない破壊的なものがあります。
そのようなのMigrationはdownメソッドで
IrreversibleMigration例外をraiseするべきです。
RailsからMigrationを実行する
RailsはMigrationを作ったり適用するのを助けるいくつかのツールを持っています。
新しいMigrationを作るには、script/generate migration MyNewMigration
を使います。ここでMyNewMigrationはこのMigrationの名前です。
このジェネレータはnnn_my_new_migration.rbというファイルをdb/migrate/ディレクトリに
作成します。nnnはmigration番号です。n個のMyNewMigrationのself.upとself.downメソッドを
書き換える事になるでしょう。
現在設定されているデータベースに対してMigrationを実行するには
rake migrate
を実行します。このコマンドは全ての未実行の
migrationを実行してデータベースを更新し、もし存在しなければschema_infoテーブルを
作成します。
データベースを前のmigrationバージョンにロールバックするには
rake migrate VERSION=X
を実行します。
Xはダウングレードしたいバージョンの番号です。
もしmigrationがIrreversibleMigration例外をスローした場合
そのステップは手作業で行う必要があります。
データベースサポート
Migrationは現在MySQL, PostgreSQL, SQLite、SQL Server、 Sybase、そしてOracleを
サポートしています。
サンプル
全てのmigrationがスキーマを変更するとは限りません。
データを修正するものもあります:
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
raise IrreversibleMigration
end
end
他にはdownではなくupの時に列を削除することもあります:
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
そして、直接SQLを実行したい場合は
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
テーブルを変えた後にそのモデルを使う場合
しばしばMigrationで列を追加して、その直後にデータを設定したい場合があるでしょう。
その場合、新しい列を追加した後、モデルにその列がある事を保証するために
Base#reset_column_informationを呼ぶ必要があります。例えば:
class AddPeopleSalary < ActiveRecord::Migration
def self.up
add_column :people, :salary, :integer
Person.reset_column_information
Person.find(:all).each do |p|
p.update_attribute :salary, SalaryCalculator.compute(p)
end
end
end
詳細の制御
デフォルトでは、Migrationは、なにを行ったかを各ステップにどのくらいかかったかの
ベンチマークとともにコンソールに出力することで表します。
ActiveRecord::Migration.verbose = falseとすることでそれらを抑制する事ができます。
また、say_with_timeメソッドを使ってベンチマークに独自のメッセージを
挿入することができます:
def self.up
...
say_with_time "Updating salaries..." do
Person.find(:all).each do |p|
p.update_attribute :salary, SalaryCalculator.compute(p)
end
end
...
end
"Updating salaries..."がブロックが終わったときのベンチマークとともに出力されます。