既存のテーブルに新たにカラムを追加する方法について。
また、作成したカラムの値をユニーク(一意)になるように設定する方法について。
既存のテーブルにカラムを追加する方法
既存のテーブルにカラムを追加するには、カラム追加用のマイグレーションファイルを作成して、マイグレーションを実行します。
マイグレーションファイルを作成するにはターミナルで次のコマンドを実行します。
rails g migration Add<カラム名>To<テーブル名> <カラム名>:<型>
Add<カラム名>To<テーブル名> の部分は、マイグレーションファイルのクラス名になります。ここでのカラム名は冒頭大文字にします。
こうすることで、マイグレーションファイルのクラス名を見ただけで、どのテーブルになんというカラムを追加する処理かが一目でわかるようになります。
後ろの <カラム名>:<型> の部分で実際に追加するカラム名と型を指定します。
実例
マイグレーションファイルの生成
Articlesテーブルにslugというカラムを文字列型で新たに追加したい場合は次のようにします。
# rails g migration AddSlugToArticles slug:string
`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead.
invoke active_record
create db/migrate/20211508103259_add_slug_to_articles.rb
するとマイグレーションファイルが生成されます。
マイグレーションファイルの中身は以下のようになっています。
class AddSlugToArticles < ActiveRecord::Migration[6.1]
def change
add_column :articles, :slug, :string
end
end
ただ、カラムを追加するだけであれば、
rails db:migrate
を実行すれば完了です。
ここでは、slugカラムに入る値が重複しない(一意)になるように設定したいので、追加で処理を記述します。
カラムの値を一意にする
カラムをユニーク(一意)に設定するには2つの方法があります。
基本的にはどちらか一方でユニークの制約をかけていれば問題ありませんが、全く同じタイミングで同時にアクセスがあった場合に2つ登録されてしまうリスクがあるため、万全を期すには両方とも設定することが望ましいです。
マイグレーションファイルで一意になるカラムを指定する(DB側で制約)
マイグレーションファイルで一意になるカラムを指定するには、対象となるカラムにインデックスをつけて、unique: true を設定します。
マイグレーションファイルのdefの中で以下のように記述します。
def change
add_index :<テーブル名>, :カラム名, unique: true
end
複数のカラムを一意になるように設定したい場合は配列の中でカラム名を指定します。
def change
add_index :<テーブル名>, [:カラム名1, :カラム名2,,,], unique: true
end
実例
先ほど生成したマイグレーションファイルに add_index を追記します。
category毎にslugをユニークにしたいため、配列でそれぞれのカラムを指定します。
class AddSlugToArticles < ActiveRecord::Migration[6.1]
def AddSlugToArticles
add_column :articles, :slug, :string
add_index :articles, [:slug, :category ], unique: true
end
end
モデルで一意になるカラムを指定する(アプリケーション側で制約)
モデルで一意になるカラムを指定するには、対象となるカラムに uniqueness: true を設定します。(モデル名は単数形。テーブル名は複数形です。)
マイグレーションファイルのdefの中で以下のように記述します。
class モデル名 < ApplicationRecord
validates :カラム名, uniqueness: true
end
複数のカラムを一意になるように設定したい場合は、一カラムずつバリデーションを指定します。
class モデル名 < ApplicationRecord
validates :カラム名1, uniqueness: true
validates :カラム名2, uniqueness: true
end
更に、他のカラムの値と掛け合わせて一意にしたい場合は scopeで空間を制限します。配列を使ってカラムは複数指定することもできます。
class モデル名 < ApplicationRecord
validates :カラム名1, uniqueness: { scope: :カラム名3 }
validates :カラム名2, uniqueness: { scope: [:カラム名3, :カラム名4] }
end
実例
例えば、追加したカラム slug が category毎に一位になるように設定する場合は以下のようになります。
class Article < ApplicationRecord
belongs_to :category, optional: true
validates :slug, uniqueness: { scope: :category }
end
必要に応じて、必須要件(presence: true,)など他のバリデーションも追加します。
マイグレーションの実行
マイグレーションするファイルがあるか確認する
まずは、マイグレーション未実施のファイルがあるか確認をします。
以下のコマンドを実行するとすべてのマイグレーションファイルの状態(実行ずみか未実行か)を一覧で表示します。
rails db:migrate:status
実行例
# rails db:migrate:status
`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead.
database: main
Status Migration ID Migration Name
--------------------------------------------------
D, [2021-07-08T07:33:19.712239 #2095] DEBUG -- : (1.5ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-07-08T07:33:19.712981 #2095] DEBUG -- : ↳ lib/active_record/connection_adapters/postgresql_adapter_patch.rb:28:in `execute_and_clear'
up 20190527000653 Create usrs
(省略)
down 20211508103259 Add slug to articles
コマンドを実行すると、マイグレーションファイルの冒頭に up や downがついた一覧が表示されています。
upがマイグレーション実行済みのファイルで、downが未実施のファイルです。
先ほど作成した、「20211508103259 Add slug to articles」のファイルは downなので、マイグレーションが未実施であることがわかります。
この状態でマイグレーションを実行すると、これらのdownのファイルがマイグレートされ、ステータスがupに切り替わります。
マイグレーションの実行
rails db:migrate でマイグレーションを実行します。
$ rails db:migrate
実例
# rails db:migrate
`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead.
D, [2021-07-08T07:33:39.999585 #2109] DEBUG -- : (0.4ms) SELECT pg_try_advisory_lock(2167604979718042620)
D, [2021-07-08T07:33:40.004272 #2109] DEBUG -- : (1.4ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-07-08T07:33:40.004685 #2109] DEBUG -- : ↳ lib/active_record/connection_adapters/postgresql_adapter_patch.rb:28:in `execute_and_clear'
I, [2021-07-08T07:33:40.006486 #2109] INFO -- : Migrating to AddSlugToArticles (20211508103260)
== 20211508103260 AddSlugToArticles: migrating ================================
-- add_column(:articles, :slug, :string)
D, [2021-07-08T07:33:40.012283 #2109] DEBUG -- : TRANSACTION (0.4ms) BEGIN
D, [2021-07-08T07:33:40.016536 #2109] DEBUG -- : (3.5ms) ALTER TABLE "articles" ADD "slug" character varying
-> 0.0056s
== 20211508103260 AddSlugToArticles: migrated (0.0057s) =======================
マイグレーションが無事実行されました。
カラムが追加されたか確認
カラムが正しく追加されているか確認します。
DBサーバーから確認することもできますが、ここではRailsのモデルを使って確認します。
Rails c で対話モードに入ってから、モデル名を指定して、column_namesメソッドを実行するだけです。
irb(main):008:0> モデル名.column_names
実例
#Railsの対話モードに入る
Rails c
#指定したモデルのカラム名のみの一覧を取得する
irb(main):008:0> Article.column_names
=> ["id", "published_at", "refreshed_at", "created_at", "updated_at", "slug"]
テーブルの末尾に、slugカラムが追加されていることがわかります。
(補足)インデックスを別のマイグレーションファイルでつける方法
上記の例では、カラムが一意となるようにインデックス付けをする際に、カラムを追加するために生成したマイグレーションファイルに追記しました。
これ以外にも、カラム追加でマイグレーションし、その後に、インデックスをつけるためのマイグレーションファイルを作成して、マイグレートする方法もあります。
コマンドラインで以下を実行すると、add_indexを行うマイグレーションファイルが生成されます。
rails generate migration add_index_<テーブル名>_<カラム名>
実例
# rails generate migration add_index_articles_slug
`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead.
invoke active_record
create db/migrate/20211508103261_add_index_articles_slug.rb
マイグレーションファイルが生成されます。
デフォルトの状態では処理は空です。
class AddIndexArticlesSlug < ActiveRecord::Migration[6.1]
def change
end
end
category毎にslugをユニークにするための処理を追記します。
class AddIndexArticlesSlug < ActiveRecord::Migration[6.1]
def change
add_index :articles, [:slug, :category ], unique: true
end
end
マイグレーションファイルが生成できたら、マイグレーションを実行します。
# rails db:migrate
`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use `exists?` instead.
D, [2021-07-08T10:02:58.652454 #2620] DEBUG -- : (0.5ms) SELECT pg_try_advisory_lock(2167604979718042620)
D, [2021-07-08T10:02:58.657499 #2620] DEBUG -- : (1.4ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-07-08T10:02:58.657950 #2620] DEBUG -- : ↳ lib/active_record/connection_adapters/postgresql_adapter_patch.rb:28:in `execute_and_clear'
I, [2021-07-08T10:02:58.660155 #2620] INFO -- : Migrating to AddIndexArticlesSlug (20211508103261)
== 20211508103261 AddIndexArticlesSlug: migrating =============================
-- add_index(:articles, [:slug, :category], {:unique=>true})
D, [2021-07-08T10:02:58.667593 #2620] DEBUG -- : TRANSACTION (0.4ms) BEGIN
D, [2021-07-08T10:02:58.675687 #2620] DEBUG -- : (6.4ms) CREATE UNIQUE INDEX "index_articles_on_slug_and_category" ON "articles" ("slug", "category)
-> 0.0095s
== 20211508103261 AddIndexArticlesSlug: migrated (0.0102s) ====================
以上で完了です。