DBマイグレーション
マイグレーションは、時間を掛けてデータベーススキームを徐々に発展させていくActiveレコードの機能です。 純粋なSQLでスキーマの編集をするより、マイグレーションはRubyのDSL(ドメイン特化言語)にテーブルへの変更を書くことで 簡単にそれを行えるようにしてくれます。 このガイドを読むことで、次の事が学べるはずです。
- ジェネレーターを使用して、マイグレーションファイルを作成する方法
- Activeレコードが提供するメソッドで、データベースを操作する方法
- Rakeタスクでマイグレーションとスキーマを操作する方法
- マイグレーションがschema.rbと、どのように関連しているか
- 1. マイグレーションの概要
- 2. マイグレーションの作成
- 3. マイグレーションの編集
- 4. マイグレーションの実行
- 5. 既に存在するマイグレーションの変更
- 6. マイグレーションでのモデルの使用
- 7. スキーマのDump
- 8. Activeレコードと参照整合性
- 9. マイグレーションとSeedデータ
1. マイグレーションの概要
マイグレーションは、データベーススキーマを簡単に一貫した方法で、時間を掛けて変更していく便利な手法です。 RubyのDSLを使ってSQLを書くこと無く、データベースに依存しないでスキーマの変更をすることができます。
各マイグレーションで行ったことを、そのデータベースの"バージョン"として考えることが出来ます。 スキーマが何もない状態から始まり、各マイグレーションがテーブル、カラム、入力情報の追加・編集・削除を行います。 Activeレコードは、マイグレーションの履歴上のどの時点のものからでも、 スキーマを現在までのタイムラインに沿って最新のバージョンに更新することが出来ます。 Activeレコードはまた、データベースを最新の構造と同じものにするdb/schema.rbファイルを更新します。
下記はマイグレーションの例になります。
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
このマイグレーションは、nameという文字列型のカラムとdescriptionというテキスト型のカラムを持つ、
productsというテーブルをを追加しています。
idという主キーが、Activeレコードモデルのデフォルトの主キーとして、暗黙的に追加されます。
timestamps
マクロは、created_at
とupdated_at
という2つのカラムを追加します。
これらの特殊なカラムは、Activeレコードによって自動的に管理されます。
状況が進行することで起こって欲しい変更を、自分達で定義することに注意してください。 このマイグレーションが実行される前はテーブルは存在せず、実行後にテーブルが存在することになります。 Activeレコードは、このマイグレーションを同様に戻す事も可能で、もしマイグレーションをロールバックすれば、 テーブルは削除されます。
トランザクションをサポートするデータベースであれば、スキーマによって状態を変更する際には、 マイグレーションはトランザクションにラップされます。 もし、データベースがトランザクションをサポートしない場合、マイグレーションが途中で失敗すると、 ロールバックする事が出来ません。 手動でロールバックを行う必要があります。
トランザクション内で実行出来ないクエリーが存在します。
もし、アダプターがDDLトランザクションをサポートする場合、
単一のマイグレーションでそれらを無効にするdisable_ddl_transaction!
を使用することが可能です。
もし、Activeレコードが戻し方を知らない、何らかのマイグレーションを実行したい場合、reversible
を使用すること出来ます。
class ChangeProductsPrice < ActiveRecord::Migration
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
あるいは、代わりにup
とdown
を使用することも出来ます。
class ChangeProductsPrice < ActiveRecord::Migration
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
2. マイグレーションの作成
2.1 スタンドアローンなマイグレーションの作成
マイグレーションは、ファイルとしてdb/migrateディレクトリに格納され、それぞれがマイグレーションクラスになっています。
ファイルの名前は、YYYYMMDDHHMMSS_create_products.rb
で、
UTCタイムスタンプ識別の後にアンダースコア区切りで、マイグレーションの名前が続いています。
マイグレーションクラスの名前(キャメルケース)は、ファイル名の後半部分と一致させる必要があります。
例えば、20080906120000_create_products.rb
ファイルのクラスはCreateProductsと定義し、
20080906120001_add_details_to_products.rb
ファイルのクラスはAddDetailsToProductsと定義すべきです。
Railsはマイグレーションを実行するべき順序を決定するのに、このタイムスタンプを使用します。
そのため、もしマイグレーションを他のアプリケーションからコピーしたり、自分でファイルを作成した際は、
この順序について注意する必要がります。
タイムスタンプをいちいち書くのは煩わしい作業のため、 Activeレコードはこのためのジェネレーターを提供してくれます。
$ rails generate migration AddPartNumberToProducts
中身は空ですが、適切なマイグレーションの名前のファイルが生成されます。
class AddPartNumberToProducts < ActiveRecord::Migration
def change
end
end
もし、下記のようにマイグレーション名を"AddXXXToYYY"、または"RemoveXXXFromYYY"の形式にし、
それに続いてカラム名と型を打ち込むと、マイグレーションはそれに合わせたadd_column
とremove_column
が定義された
ファイルを生成します。
$ rails generate migration AddPartNumberToProducts part_number:string
上記を打ち込むと、下記のファイルを生成します。
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
end
end
もし、新しいカラムにindexを追加したい場合は、次のようにします。
$ rails generate migration AddPartNumberToProducts part_number:string:index
上記を打ち込むと、下記のファイルを生成します。
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
同様にカラムを削除するマイグレーションも、コマンドラインから生成することが出来ます。
$ rails generate migration RemovePartNumberFromProducts part_number:string
上記を打ち込むと、下記のファイルを生成します。
class RemovePartNumberFromProducts < ActiveRecord::Migration
def change
remove_column :products, :part_number, :string
end
end
カラムに1つに制限されているわけではありません。 例えば、
$ rails generate migration AddDetailsToProducts part_number:string price:decimal
上記を打ち込むと、下記のファイルを生成します。
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
もし、マイグレーション名が"CreateXXX"の形式で、その後にカラム名と型を続けて打ち込むと、 マイグレーションはそのカラムを持つXXXテーブルを生成するファイルを生成します。 例えば、
$ rails generate migration CreateProducts name:string part_number:string
上記を打ち込むと、下記のファイルを生成します。
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.string :part_number
end
end
end
いつものことですが、何が生成されているのかは出発点に過ぎません。 db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb fileを編集することで、 必要に応じて追加、削除を行うことが出来ます。
また、ジェネレーターは参照(belongs_toでも利用可能な)型のカラムも受け入れます。 例えば、
$ rails generate migration AddUserRefToProducts user:references
上記を打ち込むと、下記のファイルを生成します。
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
end
end
このマイグレーションは、user_id
カラムと適切なインデックスを作成します。
名前の一部にJoinTable
を含めることで、結合テーブルを作成することも出来ます。
rails g migration CreateJoinTableCustomerProduct customer product
上記を打ち込むと、下記のファイルを生成します。
class CreateJoinTableCustomerProduct < ActiveRecord::Migration
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
2.2 モデルジェネレーター
モデルとスキャフォールドジェンレーターは、新しいモデルを追加するマイグレーションを作成します。 このマイグレーションは、っ常にテーブルと関連する構造を含むものになります。 もし、Railsに必要なカラムを伝えれば、カラムも追加された状態で生成されます。 例えば、下記を実行すると、
$ rails generate model Product name:string description:text
次のようなマイグレーションを作成します。
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
また必要に応じて、名前/型のペアとしてカラムを追加することが可能です。
2.3 型の変更
また、型の後ろに中括弧({}
)で囲っていくつかのオプションを指定することが可能です。
例えば、下記を実行すると
$ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic}
次のようなファイルを生成します。
class AddDetailsToProducts < ActiveRecord::Migration
def change
add_column :products, :price, precision: 5, scale: 2
add_reference :products, :user, polymorphic: true, index: true
end
end
3. マイグレーションの編集
ジェネレーターを使用してマイグレーションを作成したので、このファイルを編集してみましょう。
3.1 テーブルの作成
create_table
メソッドは、基本的なものの1つですが、
モデルまたはスキャフォールドジェネレータにより、生成されていることがほとんどでしょう。
典型的な使い方は次のようになります。
create_table :products do |t|
t.string :name
end
これはnameカラムを持つ(後述しますが、暗黙のidカラムも)、productsテーブルを作成します。
デフォルトでは、create_table
は、id
という名前の主キーを生成します。
:primary_key
オプションで主キーの名前を変更することも可能です。
(紐づくモデルの変更も忘れないでください。)
また、主キーが必要なければ、オプションにid: false
を渡してください。
もし、データベースに特定のオプションを指定する必要がある場合、:option
内にSQLの断片を指定します。
例えば、
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
上記は、テーブル作成時にENGINE=BLACKHOLEをSQLに追記します。 (MYSQL使用時は、デフォルトはENGINE=InnoDBになります)
3.2 結合テーブルの作成
マイグレーションメソッドのcreate_join_table
は、
HABTM(Has And Belongs To Many)の結合テーブルを作成します。
典型的な使い方は下記のようになります。
create_join_table :products, :categories
これは、category_idとproduct_idと呼ばれる2つのカラムを持つcategories_productsテーブルを作成します。
これらのカラムは、デフォルトで:null
オプションにfalseが設定されます。
:table_name
オプションを渡して、テーブル名をカスタマイズすることが可能です。
例えば、
create_join_table :products, :categories, table_name: :categorization
とすると、categorizationテーブルを作成します。
デフォルトでは、create_join_table
はオプション無しの2つのカラムを作成しますが、
:column_options
オプションを使用することで、指定することが出来ます。
例えば、
create_join_table :products, :categories, column_options: {null: true}
とすると、:null
オプションがtrueのproduct_idとcategory_idが生成されます。
create_join_table
は、ブロックの受け取りも可能なので、
下記のようにして、複数のindex(デフォルトでは作成されません)や、カラムを追加することが出来ます。
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
3.3 テーブルの変更
create_table
に近しいものに、既存のテーブルを変更するchange_table
があります。
create_table
同様の方法で使用されますが、ブロック内でより多くの事を行うことが出来ます。
例えば、
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
descriptionとnameカラムを削除し、 文字列型のpart_numberカラムを追加し、それにインデックスを付けています。 最後にupcdoeカラムの名前を変更しています。
3.4 ヘルパーだけでは十分ではない場合
もし、Activeレコードが提供するヘルパーだけでは不十分な場合、
execute
を使用することで、任意のSQLを実行することが出来ます。
Products.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
個別のメソッドのより詳しい内容や例を参照したければ、APIドキュメントを参照してください。
3.5 changeメソッドの使用
change
メソッドは、よく使用されるマイグレーションです。
ありがちなケースで、Activeレコードはマイグレーションを自動的に元に戻すことが出来ます。
現在のところ、change
メソッドは、下記のマイグレーション定義をサポートしています。
- add_column
- add_index
- add_reference
- add_timestamps
- create_table
- create_join_table
- drop_table (ブロックのみ)
- drop_join_table (ブロックのみ)
- remove_timestamps
- rename_column
- rename_index
- remove_reference
- rename_table
ブロックで、change
、change_default
、remove
を呼び出さない限り可逆的です。
もし、他のメソッドを使用する必要がある場合、
reversible
を使用するか、またはup
とdown
メソッドを、
change
メソッドの代わりに使用すべきです。
3.6 reversibleの使用
複雑なマイグレーションですと、Activeレコードでは元に戻すことが出来ないかもしれません。
元に戻すための実行で何をするかを、reversible
を使用して指定することが出来ます。
例えば、
class ExampleMigration < ActiveRecord::Migration
def change
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
reversible
は、構造を実行される正しい順番を確約してくれます。
もし、前の例のマイグレーションが戻されたのであれば、
down
ブロックは、home_page_url
が削除された後、
productテーブルがドロップされる直前に実行されます。
時に、例えばデータの削除など、マイグレーションで元に戻せないものがあります。
そのようなケースでは、down
ブロック内でActiveRecord::IrreversibleMigrationを発生させることが出来ます。
もし、誰かがマイグレーションを戻そうと試みた場合、「it can't be done.」という、エラーメッセージが表示されます。
3.7 up/down メソッドの使用
change
メソッドの代わりに、マイグレーションの古いスタイルであるup
とdown
を使用することも出来ます。
up
メソッドには、スキーマに対して行いたい変更を記述し、
down
メソッドはup
メソッドによる変更を元に戻す記述を書くべきです。
言い換えるならば、もしup
、down
を続けて実行したのであれば、データベーススキーマは不変であるべきです。
例えば、もしup
メソッド内でテーブルを作成する場合、down
メソッド内でテーブルをドロップすべきです。
元に戻すための変更は、up
メソッドによって実行された変更の反対の順番で実行するのが賢明です。
下記は、reversible
句と同等です。
class ExampleMigration < ActiveRecord::Migration
def up
create_table :products do |t|
t.references :category
end
# add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
drop_table :products
end
end
もし、マイグレーションが不可逆的なものであれば、down
メソッド内でActiveRecord::IrreversibleMigrationを発生させるべきです。
誰かがマイグレーションを戻そうとすると、エラーメッセージとして「it can't be done.」が表示されます。
3.8 前のマイグレーションに戻す
revert
メソッドを使用することで、Activeレコードのロールバックマイグレーションの機能を使用することが出来ます。
require_relative '2012121212_example_migration'
class FixupExampleMigration < ActiveRecord::Migration
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
また、revert
メソッドは、元に戻すための指示が書かれたブロックを受け入れます。
これは、前のマイグレーションの一部を選択して元に戻す際に便利です。
例えば、前述のExampleMigrationが既にコミットされており、
後ほどProductリストを、代わりにシリアライズすることが最良だと判断したようなケースを想像してください。
シリアライズのためのマイグレーションを下記のように書くことが出来ます。
class SerializeProductListMigration < ActiveRecord::Migration
def change
add_column :categories, :product_list
reversible do |dir|
dir.up do
# Category#product_listによるProductsから、データを移すコード
end
dir.down do
# Category#product_listからProductsを作成
end
end
revert do
# ExampleMigrationからコピー&ペースト
create_table :products do |t|
t.references :category
end
reversible do |dir|
dir.up do
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
end
end
# The rest of the migration was ok
end
end
end
同じマイグレーションをrevert
を使わずに書くことも可能ですが、
その場合は更にいくつかのステップを踏むことが必要になります。
create_table
とreversible
を順番に元に戻し、
create_table
をdrop_table
に入れ替え、
最後にup
をdown
に入れ替え、逆の場合も同等です。
4. マイグレーションの実行
Railsは、マイグレーションのセットを実行させるRakeタスクのセットを提供します。
最初にマイグレーションで使用することになるRakeタスクは、おそらくrake db:migrate
になるでしょう。
これは最も基本的なフォームで、まだ実行されていない全てのマイグレーションのchange
、またはup
メソッドを実行します。
もし、そのようなマイグレーションが無ければ、何もせずに終了します。
マイグレーションの日付を元に、その日付順でマイグレーションは実行されます。
db:migrate
はまた、db:schema:dump
も実行ことに注意してください。
これは、db/schema.rb
ファイルをデータベースの構造にあった状態に更新します。
もし、バージョンを指定した場合、Activeレコードはその指定したバージョンに達するまでの必要なマイグレーション(change、up、down)を実行します。 バージョンは、マイグレーションファイルの数値の接頭辞(プレフィックス)になります。 例えば、バージョン20080906120000の指定をして実行するのであれば、下記のようにして実行します。
$ rake db:migrate VERSION=20080906120000
もし、バージョン20080906120000が現在のバージョンより上のバージョンで、マイグレーションが進む状態である場合、
20080906120000バージョンを含む全てのマイグレーションを上げる、change
(またはup
)メソッドが実行され、
それより後のマイグレーションは実行されません。
もし、マイグレーションが戻る状態である場合、20080906120000バージョンを含まないマイグレーションを下げる全てのdown
が実行されます。
4.1 ロールバック
一般的なタスクは、最後に実行したマイグレーションをロールバックすることができます。 例えば、ミスをしてこれを修正したいと考えているとします。 前のマイグレーションを辿ってバージョンを戻すマイグレーションを作るのではなく、下記を実行することでロールバックすることが出来ます。
$ rake db:rollback
これは直近のマイグレーションを、change
メソッド、またはdown
メソッドによって元に戻します。
もし、いくつかのマイグレーションを戻す必要がある場合は、STEPパラメーターを使用してそれを行うことが可能です。
$ rake db:rollback STEP=3
これは直近の3つのマイグレーションを戻します。
db:migrate:redo
タスクは、ロールバックをし、再度そのマイグレーションを実行する処理のショートカットです。
db:rollback
タスクとして、1つ以上のバージョンを戻す必要がある場合は、下記のようにしてSTEPパラメーターを使用することが可能です。
$ rake db:migrate:redo STEP=3
これらのRakeタスクはどれも、db:migrateを使って出来ないことを行えるわけではありません。 マイグレーション時にバージョンを明確に指定する必要がないため、単に便利であるという事に過ぎません。
4.2 データベースのリセット
rake db:reset
タスクは、データベースをドロップ、再構築し、最新のスキーマを読み込みます。
これは全てのマイグレーションを実行する事とは異なります。
現在のschema.rbファイルの内容を使用しているに過ぎません。
もし、マイグレーションがロールバック出来ない場合は、rake db:reset
が有効かもしれません。
更に詳しく知りたければ、このページの'schema dumping and you.'を参照してください。
4.3 特定のマイグレーションの実行
もし、特定のup
、またはdown
マイグレーションを実行する必要がある場合、
db:migrate:up
とdb:migrate:down
でそれを行います。
適切なバージョンを指定するだけで、一致するマイグレーションのchange
、up
、
またはdown
が実行されます。例えば、
$ rake db:migrate:up VERSION=20080906120000
は、20080906120000マイグレーションが,change
メソッド(または、up
メソッド)によって実行されます。
このタスクはまず、マイグレーションが既に実行されているかどうかを調べ、もしActiveレコードが既に実行されていると判定すれば何もしません。
4.4 異なる環境下でのマイグレーションの実行
デフォルトではrake db:migrate
は開発環境下で実行されます。
他の環境に対してマイグレーションを実行するには、コマンド実行時にRAILS_ENV
を使用して環境変数を指定します。
例えば、test環境でマイグレーションを実行したければ、下記のようにします。
$ rake db:migrate RAILS_ENV=test
4.5 マイグレーション実行出力の変更
デフォルトでマイグレーションは、何をしたか、どれぐらい時間が掛かったかを正確に教えてくれます。 マイグレーションは、テーブルを作成し、このような出力を行います。
== CreateProducts: migrating ======================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) =============
開発者が好きなように制御することが出来るメソッドが、マイグレーションから提供されています。
メソッド | 目的 |
---|---|
suppress_messages | 引数としてブロックを取り、ブロックによって生成された出力を抑制します。 |
say | 引数をメッセージとして出力します。 2つ目の真偽値の引数には、インデントをするか否かの指定します。 |
say_with_time | ブロックを実行するのに掛かった時間をテキストで出力します。 ブロックが整数を返す場合、それは影響を受けた行数であるとみなされます。 |
例えば、
class CreateProducts < ActiveRecord::Migration
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created a table"
suppress_messages {add_index :products, :name}
say "and an index!", true
say_with_time 'Waiting for a while' do
sleep 10
250
end
end
end
次のように出力されます。
== CreateProducts: migrating ===========================
-- Created a table
-> and an index!
-- Waiting for a while
-> 10.0013s
-> 250 rows
== CreateProducts: migrated (10.0054s) =================
もし、Activeレコードに何も出力させたくなければ、
rake db:migrate VERBOSE=false
を実行すれば、全ての出力が表示されなくなります。
5. 既に存在するマイグレーションの変更
時折、間違ったマイグレーションを書いてしまうことがあるかもしれません。
もし、既にそのマイグレーションを実行してしまうと、そのマイグレーションを編集して、再度マイグレーションを実行することが出来ません。
Railsは既にマイグレーションを実行したとみなし、rake db:migrate
を実行しても何もしません。
そういった場合、マイグレーションをロールバック(例えば、rake db:rollback
を使用して)し、
マイグレーションを修正して、正しいものをrake db:migrate
で実行すべきです。
通常、既存のマイグレーションを編集することはお勧めできません。 もし、既存のバージョンのマイグレーションがproduction環境上で実行されていた場合、 余計な仕事と頭痛の種が増えることになるでしょう。 代わりに、その問題を修正するマイグレーションを書くべきです。 新しく生成したマイグレーションを、適用する前(より分かりやすく言うと、他の開発マシンに広がる前)に編集することについては、 それほど害はないはずです。
revert
メソッドは、新しいマイグレーションを前のマイグレーションに、一部または全てを戻す際に便利です。
(上述の、「3.8 前のマイグレーションに戻す」を参照してください。)
6. マイグレーションでのモデルの使用
データの作成、または更新をマイグレーションで行う際に、自分で定義したモデルを使用したくなるかもしれません。 Railsは、それを基盤としてデータに簡単にアクセスするための機能を提供してくれています。 ただし、それを行うにはいくつか注意すべき項目があります。
例えば、その時点ではデータベース上に存在しないカラムを、(1)その時点で、または(2)それに続くマイグレーション時に、 モデルが作成しようとした場合に問題が発生します。
以降のの例で、どういう事か分かりやすく説明します。
アリスとボブがProductモデルが含まれる同じコードを共有して作業していたとします。
ボブが休暇を取りました。
アリスはproductsテーブルに新しいカラムを追加し、それを初期化するマイグレーションを作りました。 また、Prodcutモデルのその新しいカラムに対して、検証(Validation)処理も追加しました。
# db/migrate/20100513121110_add_flag_to_product.rb
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean
reversible do |dir|
dir.up { Product.update_all flag: false }
end
Product.update_all flag: false
end
end
# app/models/product.rb
class Product < ActiveRecord::Base
validates :flag, presence: true
end
アリスは次に、productsテーブルに先程とは異なるカラムを追加し、それを初期化するためのマイグレーションを追加しました。 Productモデルには、その新しいカラムに対しての検証(Validation)処理も追加しました。
# db/migrate/20100515121110_add_fuzz_to_product.rb
class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
reversible do |dir|
dir.up { Product.update_all fuzz: 'fuzzy' }
end
end
end
# app/models/product.rb
class Product < ActiveRecord::Base
validates :flag, :fuzz, presence: true
end
アリスの作業環境で、両方のマイグレーションが適用されました。
ボブが休暇から帰ってきました。そして…、
- 両方のマイグレーションと最新のバージョンのProductモデルが含まれるソースコードに更新
- 更新されたProductモデルが含まれた状態で、
rake db:migrate
によるマイグレーションを実行
ここで、マイグレーションの実行に失敗します。 何故なら、モデルが保存を試みた際に、最初のマイグレーション実行時点では、2番目に追加されるはずのカラム(fuzz)はその時点ではデータベース上に存在しないのに、 2番目に追加されたカラム(fuzz)の検証をしようとしてしまうからです。
rake aborted!
An error has occurred, this and all later migrations canceled:
undefined method `fuzz' for #<Product:0x000001049b14a0>
解決するには、マイグレーションを考慮したモデルをローカル環境で作成し、 Railsが検証をパスしてマイグレーションの実行を完了できるようにします。
ローカルのモデルを使用する際に、Product.reset_column_information
を呼んで、
データベースのデータ更新の前に、ProductモデルのためにActiveレコードのキャッシュをクリアしておくと良いでしょう。
もし、アリスが下記のようにしていれば、問題は発生しなかったでしょう。
# db/migrate/20100513121110_add_flag_to_product.rb
class AddFlagToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
add_column :products, :flag, :boolean
Product.reset_column_information
reversible do |dir|
dir.up { Product.update_all flag: false }
end
end
end
# db/migrate/20100515121110_add_fuzz_to_product.rb
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
add_column :products, :fuzz, :string
Product.reset_column_information
reversible do |dir|
dir.up { Product.update_all fuzz: 'fuzzy' }
end
end
end
上記の例とは別の更に悪いケースがありえます。
例えば、アリスが特定のdescriptionフィールドを更新するマイグレーションを作成したとします。 彼女はマイグレーションを実行して、そのコードをコミットし、新しい機能のためにproductテーブルにfuzzカラムを追加する作業を始めます。
彼女はその新しい機能のために2つのマイグレーションを作成します。 1つ目は新しいカラムの追加、2つ目は他のproductの属性を元に、特定のfuzzカラムを更新します。
このマイグレーションの実行では特に問題は起こりませんが、
ボグが休暇から帰ってきてrake db:migrate
によって未実行の全てのマイグレーションを実行すると、
巧妙なバグが紛れ込んでしまいます。
descriptionは初期化し、fuzzカラムが提供されますが、fuzzは全てnilになります。
この解決策も、マイグレーションがProductモデルを参照する前に、Product.reset_column_information
を使用して、
レコードのデータが操作される前にActiveレコードが覚えるべき現在のテーブル構造を確立することです。
7. スキーマのDump
7.1 schemaファイルは何のためにあるのか?
マイグレーションは、DBスキーマとして信頼できるソースではありません。 その役割を担うのは、db/schema.rb、もしくはデータベースを調べてActiveレコードによって生成されたSQLファイルです。 それらは現在のデータベースの状態を表したもので、編集されるように設計されていません。
アプリケーションを再構築する際に、全てのマイグレーションの履歴を辿ったデプロイをする必要はありません。(しかも、エラーになる傾向があります) 現在のスキーマの記述を読み込んむ事で、よりシンプルに素早くそれを行うことが出来ます。
例えば、これはtestのデータベースを作成する方法ですが、 現在のdevelopmentデータベースをダンプし、(db/schema.rbまたは、db/structure.sql) それをtestデータベースに読み込みます。
またschemaファイルは、Activeレコードオブジェクトが何の属性を持つか素早く確認したいような場合にも便利です。 この情報はモデルのコードではなく、複数のマイグレーションの情報にまたがって構成されていますが、 schemaファイルに上手に要約されています。 annotate_modelsgemは、 その機能を希望すれば、自動で各モデルの上部にスキーマの要約のコメントの追加と更新を行います。
7.2 Schema Dumpsのタイプ
スキーマのdumpには2つの方法があります。
これは、config/application.rb内のconfig.active_record.schema_formatに、
:sql
または:ruby
を指定することで設定されます。
:ruby
が選択された場合、スキーマ情報はdb/schema.rbに格納されます。
このファイルを参照すると、かなりボリュームのあるマイグレーションファイルであることに驚くかもしれません。
ActiveRecord::Schema.define(version: 20080906171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "part_number"
end
end
このファイルはデータベースを調べ、それを元にcreate_table
とadd_index
で構造を表現します。
こうすることでデータベースの種類に依存しなくなり、Activeレコードのサポートによってデータベースに読み込むことが可能になります。
これは、複数のデータベースで使用可能なものとして、アプリケーションを配布する際に非常に便利です。
しかし、db/schema.rbは外部キー制約、トリガー、ストアド・プロシージャなどのデータベース固有のものは表現出来ないため、それとのトレードオフになります。
また、DB固有のSQLを直接実効するようなマイグレーションがある限り、スキーマのdumpによってデータベースを再構築することは不可能になります。
もし、今挙げたようなことを行いたいのであれば、:sql
フォーマットを使用すべきです。
Activeレコードのスキーマのdumpの代わりにそのデータベースの構造を、 データベース固有のツールを使用して(db:structure:dumpのRakeタスクを通して)db/structure.sqlにdumpします。 例えば、PostgreSQLであればpg_dumpユーティリティが使用されます。 MySQLであれば、SHOW CREATE TABLEによる出力が含まれす。
これらのスキーマを読み込むことに関して、これに含まれるSQL実行文がどうなるかという疑問があると思います。
定義上は、データベース構造の完全なコピーを作成します。
スキーマフォーマットに:sql
を指定した場合、それが作成されることに使用されないものに関しては、
RDBMSへの読み込みをしないようにします。(翻訳に自信無し)
7.3 スキーマのdumpとソースの制御
スキーマがdumpするものは、データベースの構造として信頼のおけるソースであるため、 そのソースがどのようなものであるか確認することを強くお勧めします。
8. Activeレコードと参照整合性
Activeレコードはデータベースではなく、モデルに従って処理を行います。 そのため、トリガーや外部キー制約のような機能は頻繁に使用されません。(翻訳に自信無し)
validates :foreign_key
、uniqueness: true
のような検証は、データの正当性を保持するための1つの方法です。
関連性を表す:dependent
オプションは、親のデータに基づく子データを、親データが削除された際に自動的に削除します。
これらはアプリケーション層の制御であるため、完全なデータを保証することが出来ず、
そのため一部の人々は、データベースの外部キー制約でそれを補おうとします。
Activeレコードは、そのような機能を備えたツールを提供しませんが、
execute
メソッドは、任意のSQLを実行するのに使用することが出来ます。
また、foreignerのようなgemを使用して、
Activeレコードに外部キーサポート(db/schema.rbへの外部キーサポートも含みます)を追加することも可能です。
9. マイグレーションとSeedデータ
一部の々は、データの追加にマイグレーションを使用します。
class AddInitialProducts < ActiveRecord::Migration
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
ただし、Railsは'seeds'と呼ばれる初期データを作成するための機能を持っています。
これはとてもシンプルな機能で、db/seeds.rbにRubyコードで処理を下記、rake db:seed
で実行します。
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
空のアプリケーションのデータベースのセットアップに、よく使用されます。
© 2010 - 2017 STUDIO KINGDOM