2007/06/24

Rails Migration

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..."がブロックが終わったときのベンチマークとともに出力されます。

0 件のコメント: