はじめに
開発している際に、マイグレーションファイルを操作することがあるかと思いますが、操作頻度の割に知識が少ないんじゃ無いかな?と不安になったので調べてみました。
この記事を見れば、かなりRails開発でのマイグレーション操作に強くなるかと思います!
マイグレーションの概要
マイグレーションは、データベーススキーマの継続的な変更(英語)を、統一的かつ簡単に行なう
ための手法です。
マイグレーションではRubyのDSLを使っているので、生のSQLを作成する必要がなく、スキーマとスキーマへの変更をデータベースの種類に依存せずに済みます。
1つ1つのマイグレーションは、データベースの新しい'version'とみなすことができます。
スキーマは空の状態から、マイグレーションを実行することで変更を加えていきます。
ActiveRecordはdb/schema.rbファイルを更新し、データベースの最新の構造と一致
するようにします。
マイグレーションの作成
マイグレーションを作成するには以下のコマンドを実行します。
マイグレーションファイル名はYYYYMMDDHHMMSS_create_products.rb
のような形式になります。
$ rails g migration AddPriceToProducts
#=> 20210516120000_add_price_to_products.rb
# カラムとデータ型を指定
$ rails g migration AddPariceProducts pricer:integer
# 複数指定も可能
$ rails g migration AddPariceProducts pricer:integer name:string
# priceカラムを削除
$ rails g migration RemovePriceToProducts
#=> 20210516120000_remove_price_to_products.rb
モデルの作成
モデルのジェネレータとscaffoldジェネレータは、新しいモデルを追加するマイグレーションを生成します。このマイグレーションには、関連するテーブルを作成する命令が既に含まれています。必要なカラムを指定すると、それらのカラムを追加する命令も同時に生成されます。
$ rails g model Product name:string
#=> 20210516120000_create_products.rb
# データ型のデフォルトはstringなので省略可能
$ rails g model Product name
#=> 20210516120000_create_products.rb
コマンドを実行すると以下のようなファイルが生成される。
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
# nameカラム作成
t.string :name
# created_atカラム、updated_atカラム作成
t.timestamps
end
end
end
マイグレーションの自作
コマンドで簡単に作ることは出来ますが、自作で自由にマイグレーションファイルを作成することも可能です。
テーブルの作成
テーブルを作成する時は、create_table
メソッドを使用します。
# productsテーブルを作成
create_table :products do |t|
# nameカラム作成
t.string :name
end
テーブル結合を作成
has_and_belongs_to_manyテーブル結合(join)を作成する際は、create_join_table
メソッドを使用します。
指定したテーブルのモデル名_id
は自動で定義されます。
# 引数の最初の2つをつなげたものが結合し、categories_productsテーブルを作成
create_join_table :products, :categories do |t|
# product_idとcategory_idにインデックスを追加
t.index :product_id
t.index :category_id
end
テーブルの変更
既存のテーブルを変更するには、change_table
メソッドを使用します。
基本的にはcreate_tableメソッドと同じ要領で使います。
change_table :products do |t|
# price、nameカラムを削除
t.remove :price, :name
# products_numberカラムを生成、インデックスを追加
t.string :products_number
t.index :products_number
# upccodeカラムを、upc_codeにカラム名を変更
t.rename :upccode, :upc_code
end
カラムの操作
カラムの追加
add_column
で指定したカラムを追加することが出来ます。
class AddPriceToProducts < ActiveRecord::Migration[5.0]
def change
# Productsテーブルにpriceカラムを追加
add_column :products, :price, :integer
end
end
カラムの削除
remove_column
では逆に指定したカラムを削除することが出来ます。
class RemovePriceFromProducts < ActiveRecord::Migration[5.0]
def change
# Productsテーブルのpriceカラムを削除
remove_column :products, :price, :integer
end
end
カラムの変更
change_column
ではカラムのデータ型を変更することが出来ます。
# productsテーブルのproducts_numberカラムをtext型に変更
change_column :products, :products_number, :text
# productsテーブルのnameカラムのNotNull制約をfalseにする
change_column_null :products, :name, false
# productsテーブルのapprovedカラムのデフォルト値を指定
change_column_default :products, :approved, from: true, to: false
カラム修飾子
修飾子 | 意味 |
---|---|
limit | 最大サイズを設定 |
precision | decimalフィールドの精度 (precision) を定義 |
scale | decimalフィールドの精度 (スケール: scale) を指定 |
polymorphic | belongs_to関連付けで使うtypeカラムを追加 |
null | trueでNULL値を許可、falseで禁止 |
default | カラムでのデフォルト値の設定 |
comment | カラムにコメントを追加 |
外部キー
add_foreign_key
メソッドで外部キー制約を追加することができます。
Railsでは、すべての外部キーはfk_rails_という名前で始まり、その後ろにfrom_tableとcolumnから一意に生成された文字列が10文字追加されます。 必要であれば、:nameオプションを指定することで別の名前を使えます。
# articlesテーブルのauthor_idカラムを追加
add_foreign_key :articles, :authors
# 削除するカラム名の決定をActive Recordに任せる場合
remove_foreign_key :accounts, :branches
# カラムを指定して外部キーを削除する場合
remove_foreign_key :accounts, column: :owner_id
# 名前を指定して外部キーを削除する場合
remove_foreign_key :accounts, name: :special_fk_name
通常とは逆の動作を指定
マイグレーションを通常どおり実行する場合と逆転する場合の動作を指定する際は、reversible
メソッドを使用します。
class ExampleMigration < ActiveRecord::Migration[5.0]
def change
create_table :distributors do |t|
t.string :zipcode
end
reversible do |dir|
dir.up do
# CHECK制約を追加
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5) NO INHERIT;
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
end
up/downメソッド
changeの代りに、従来のupメソッドとdownメソッドを使うこともできます。
upメソッドにはスキーマに対する変換方法を記述し、downメソッドにはupメソッドによって行われた変換を逆転する方法を記述する必要があります。つまり、upの後にdownを実行した場合、スキーマが変更されないようにする必要があります。 たとえば、upメソッドでテーブルを作成したら、downメソッドではそのテーブルを削除する
必要があります。
downメソッド内では、upメソッド内で行なうのとは逆順にするのが主流です。
class ExampleMigration < ActiveRecord::Migration [5.0]
def up
create_table :distributors do |t|
t.string :zipcode
end
# CHECK制約を追加
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
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 distributors
DROP CONSTRAINT zipchk
SQL
drop_table :distributors
end
end
マイグレーションの実行
主にマイグレーションを実行するrailsコマンドで使うのは、ほとんどの場合rails db:migrate
コマンドだと思います。現在、実行されていないchangeまたはupメソッドを実行します。マイグレーションの実行順序は、マイグレーションの日付に基づきます。
# 通常のマイグレーション実行
$ rails db:migrate
# バージョン(マイグレーションファイル名の冒頭に付いている数字)指定も可能
$ rails db:migrate VERSION=20080906120000
ロールバック
マイグレーションがロールバックを実行するには、rails db:rollback
を実行します。
changeメソッドを逆転実行するかdownメソッドを実行します。
# 直前のマイグレーションがロールバック
$ rails db:rollback
# 最後から指定した数だけロールバックする
$ rails db:rollback STEP=3
# ロールバックと再マイグレーションを一度に実行
$ rails db:migrate:redo
データベースの設定
rails db:setup
コマンドを実行することで、データベースの作成、スキーマの読み込み、シードデータを用いてデータベースの初期化を実行します。
データベースをリセットする
rails db:reset
コマンドは、データベースをdropして再設定します。
このコマンドは、すべてのマイグレーションを実行することと同じ動作ではありません。このコマンドでは現在のschema.rbの内容をそのまま使い回しています。
特定のマイグレーションの実行
特定のマイグレーションを実行する場合は、rails db:migrate:up
またはrails db:migrate:down
を実行します。該当するマイグレーションに含まれるchange、up、downメソッドのいずれかが呼び出されます。
# up方向にマイグレーションを実行
$ rails db:migrate:up
# down方向にマイグレーションを実行
$ rails db:migrate:down
# バージョン指定して実行
$ rails db:migrate:up VERSION=20080906120000
異なる環境でマイグレーションを実行
デフォルトでは、development環境でマイグレーションは実行されます。
別の環境に対してマイグレーションを行いたい場合は、コマンド実行時にRAILS_ENV環境変数を指定します。
# test環境でマイグレーション実行
$ rails db:migrate RAILS_ENV=test
マイグレーション実行結果の出力変更
デフォルトでは、マイグレーション実行後に正確な実行内容とそれぞれの所要時間が出力されます。
マイグレーションには、これらの出力方法を制御するためのメソッドが提供されています。
メソッド | 目的 |
---|---|
suppress_messages | 引数としてブロックを1つ取り、そのブロックでの出力をすべて抑制 |
say | 引数としてメッセージを1つ受け取り、それをそのまま出力 |
say_with_time | 受け取ったブロックを実行するのにかかった時間を示すテキストを出力 |
class CreateProducts < ActiveRecord::Migration[5.0]
def change
# do 〜 end内の出力を抑制
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 }
# 指定したメッセージを出力し、出力をインデントするためにtrueを渡す
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) =======================================
既存のマイグレーションの変更
マイグレーションを自作していると、ときにはミスしてしまうこともあります。Railsでは既存のマイグレーションを直接変更するのは一般的に良しとされていません。
こういった場合は、既存のマイグレーションを直接修正せず、修正用のマイグレーションを新たに作成してそれを実行するのが正しい方法です。
revert
メソッドは、以前のマイグレーション全体またはその一部を取り消すためのマイグレーションを新たに書くときにも便利です。
スキーマダンプの意義
スキーマファイルの意味
Railsは、デフォルトでデータベーススキーマの最新の状態のキャプチャを試みるdb/schema.rb
を生成します。
アプリケーションのデータベースの新しいインスタンスを作成する場合、マイグレーションの全履歴を一から繰り返すよりも、rails db:schema:loadでスキーマファイルを読み込む方が、高速かつエラーが起きにくい傾向があります。
スキーマダンプの種類
Railsで生成されるスキーマダンプのフォーマットは、config/application.rb
のconfig.active_record.schema_format
設定で制御されます。デフォルトのフォーマットは:rubyですが、:sqlも指定できます。
ActiveRecord::Schema.define(version: 2021_05_16_123456) do
create_table "users", 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.integer "price"
t.datetime "created_at"
t.datetime "updated_at"
t.string. "products_number"
end
end
db/schema.rbでは、トリガ/シーケンス/ストアドプロシージャ/チェック制約などのデータベース固有の項目を表現できません。これらの機能が必要な場合は、新しいデータベースインスタンスの作成に有用なスキーマファイルを正確に得るためにスキーマのフォーマットを:sqlにする必要があります。
スキーマフォーマットを:sqlにすると、データベース固有のツールを用いてデータベースの構造をdb/structure.sql
にダンプします。
スキーマをdb/structure.sqlから読み込む場合、rails db:structure:load
を実行します。
これにより、含まれているSQL文が実行されてファイルが読み込まれます。定義上、これによって作成されるデータベース構造は元の完全なコピーとなります。
Active Recordと参照整合性
Active Recordは、「知的に振る舞うのはモデルであり、データベースではない」というコンセプトに基づいています。
validates :foreign_key
やuniqueness: true
のようなデータベース検証機能は、データ整合性の強制をモデルが行っている例です。
Active Recordだけではこうした外部機能を扱うツールをすべて提供することはできませんが、execute
メソッドを使えば任意のSQLを実行できます。
マイグレーションとシードデータ
Railsには、データベース作成後に初期データを追加するためのseed機能があります。
シードは、development環境やtest環境で頻繁にデータを再読込する場合に特に便利です。シード機能は、db/seeds.rbにRubyコードを記述してrails db:seed
を実行するだけ使用出来ます。
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
productionのデータベースのような削除や再作成を行えない既存データベースでは、以下のようにデータの追加や変更をすると便利です。
class AddInitialProducts < ActiveRecord::Migration[5.0]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
古いマイグレーション
db/schema.rb
やdb/structure.sql
は、使っているデータベースの最新ステートのスナップショットであり、そのデータベースを再構築するための情報源として信頼できます。
db/migrate/ディレクトリ内のマイグレーションファイルを削除しても、Rails内部のschema_migrationsという名前のデータベース内に保存されている(マイグレーションファイル固有の)マイグレーションタイムスタンプへの参照を保持し続けます。
マイグレーションファイルを削除した状態でrails db:migrate:status
コマンドでマイグレーションファイルの状態を確認すると、削除したマイグレーションファイルの後に********** NO FILE **********
と表示されているかと思います。
マイグレーションファイルが特定の環境で一度実行されたが、db/migrate/配下に見当たらない場合に表示されます。
終わりに
マイグレーションを操作するには、色んな動作が出来ますね!
その分使いこなすのは難しいかもしれませんが、使っていけば徐々に理解していくことが出来ると思います。
よく使う操作ではあると思うので、頑張って習得しましょう!