Rails側のモデルに即した形でデータベース側のテーブル設定を変更したりする際に役立つマイグレーション機能、およびその関連事項について簡単にまとめてみます。マイグレーションはデータベースのテーブルレイアウトの差分に相当します。ある状態のテーブルレイアウトから、その差分を用いて新たなテーブルレイアウトにマイグレート (移行) するのです。
アプリケーションの開発時に用意する最初のマイグレーションファイルは、scaffold機能で生成されるものを使用すればよいです。
$ rails new myApp
$ rails generate scaffold myModel field1:string field2:integer field3:date field4:boolean
field5:text field6:decimal field7:binary field8:datetime
(↑db/migrate/20140617113217_create_my_models.rb が自動生成されました)
$ rake db:migrate
開発中または運用中に新たなテーブルレイアウトに移行するためのマイグレーションファイルを生成する場合は、下記のようにします。
$ rails generate migration AddFieldAToMyModels firldA:string
(↑db/migrate/20140617114356_add_field_a_to_my_models.rb が生成されました)
$ rake db:migrate
上記2つの方法で生成可能なマイグレーションファイルには生成された時刻がバージョンとして付与されます。Railsは、DBにどのバージョンのマイグレーションファイルまで反映されたかを記録するために、DB内に "schema_migrations" というテーブルを保持しています。試しに "rails db" でコンソールに入って確かめてみます。
sqlite> .tables
my_models schema_migrations
sqlite> .schema schema_migrations
CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
sqlite> select * from schema_migrations;
20140617091246
なお、バージョン状態を確認するだけであれば、わざわざコンソールに入らずとも、
$ rake db:migrate:status
とすることで確認できます。実行結果例:
database: c:/Users/path/to/myApp/db/development.sqlite3
Status Migration ID Migration Name
--------------------------------------------------
up 20140617091246 Create my models
db/migrate/20140617113217_create_my_models.rb (新規テーブルレイアウト)
class CreateMyModels < ActiveRecord::Migration
def change
create_table :my_models do |t|
t.string :field1, limit: 20, null: false # VARCHAR(20), NOT NULL
t.integer :field2, default: 99 # DEFAULT 99
t.date :field3
t.boolean :field4
t.text :field5 # stringはDBではVARCHAR型, textはDBではTEXT型
t.decimal :field6 # 小数
t.binary :field7 # 画像データなどを格納
t.datetime :field8
t.timestamps # created_at, updated_at を生成
end
end
end
マイグレーション結果例
sqlite> .schema my_models
CREATE TABLE "my_models" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"field1" varchar(20) NOT NULL,
"field2" integer DEFAULT 99,
"field3" date,
"field4" boolean,
"field5" text,
"field6" decimal,
"field7" blob,
"field8" datetime,
"created_at" datetime,
"updated_at" datetime);
db/migrate/20140617114356_add_field_a_to_my_models.rb (テーブルレイアウトの差分)
class AddFieldAToMyModels < ActiveRecord::Migration
def change
change_table :my_models do |t|
t.string :fieldA #add
t.remove :field1, :field2
t.rename :field3, :field3_renamed
end
add_index :my_models, :field4
add_index :my_models, [:field4, :field5] # マルチカラムインデックス
# remove_index :my_models, :field4
# remove_index :my_models, column: [:field4, :field5]
# HABTM (Has And Belongs To Many: m:nの関係にある2つのモデル)
# create_join_table :my_models, :habtm_models # ←中間テーブルを生成
end
end
マイグレーション結果例
sqlite> .schema my_models
CREATE TABLE "my_models" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"field3_renamed" date,
"field4" boolean,
"field5" text,
"field6" decimal,
"field7" blob,
"field8" datetime,
"created_at" datetime,
"updated_at" datetime,
"fieldA" varchar(255));
sqlite> .indices
index_my_models_on_field4
index_my_models_on_field4_and_field5
unique_schema_migrations
$ rake db:drop:all
$ rake db:migrate VERSION=20140617113217
$ rake db:migrate:reset
test/fixtures/my_models.yml
label_1:
field1: MyStringOne
field2: 1
label_2:
field1: MyStringTwo
field2: 2
<% 3.upto(10) do |i| %>
label_<%= i %>:
field1: MyString
field2: <%= i %>
<% end %>
上記例のように、rubyスクリプトを埋め込めます。用意ができたら下記コマンドを実行してデータをDBに流し込みましょう。
$ rake db:fixtures:load FIXURES=my_models
test/fixtures/related_model.yml (仮)
related_1:
my_model: label_1
...
アソシエートされたモデルについてはラベルを利用可能です。上記例では、my_model_idにidで指定するのではなく、my_modelにラベルを指定しています。my_model_idには適当なIDが自動で設定されます。
$ rake db:fixtures:load FIXURES=my_models,related
マイグレーションファイルという差分を積分していった結果、つまり最新のテーブルレイアウトは、"db/schema.rb" に記述されています。マイグレートする度にこのファイルは自動で更新されます。Drop直後や新しい環境ではマイグレーションを繰り返し実行するのではなく、このこのスキーマファイルを読み込みましょう。
$ rake db:schema:load
同様に、マイグレーションの繰り返しではなくスキーマでリセットしたい場合は、"rake db:migrate:reset" ではなく
$ rake db:reset
としましょう。
db/seeds.rb
MyModel.create(fieldA: 'str1')
MyModel.create(fieldA: 'str2')
というファイルを用意して、下記コマンドを実行します。
$ rake db:seed
ちなみに、"db:schema:load" および "db:seed" を合わせたコマンドとして、
$ rake db:setup
があります。新しい環境への導入時等に使用しましょう。
"fixture" や "migrate" を実行する対象となる開発環境は、既定では "development" ですが、"config/database.yml" に指定のある他の環境に対して実行したい場合はパラメータとして明示する必要があります。
$ rake db:migrate RAILS_ENV=production
既出の事項も含まれますが、実運用でロールバック関連の作業をする際には以下のようにします。
一つだけ戻す
$ rake db:rollback
$ rake db:migrate:status
database: mydb_development
Status Migration ID Migration Name
--------------------------------------------------
up 20150620101651 Create users
down 20150814181311 Rename users column
再度適用
$ rake db:migrate
$ rake db:migrate:status
database: mydb_development
Status Migration ID Migration Name
--------------------------------------------------
up 20150620101651 Create users
up 20150814181311 Rename users column
今度は二つ戻す
$ rake db:rollback STEP=2 ←大文字であることに注意
$ rake db:migrate:status
database: mydb_development
Status Migration ID Migration Name
--------------------------------------------------
down 20150620101651 Create users ← DROP TABLE されるためデータは消失します
down 20150814181311 Rename users column