【Rails】既存のテーブルにカラムを追加する方法とカラムをユニーク(一意)に指定する方法

rals-how-to-add-column-and-set-unique Rails
記事内に広告が含まれていることがあります。

既存のテーブルに新たにカラムを追加する方法について。

また、作成したカラムの値をユニーク(一意)になるように設定する方法について。

既存のテーブルにカラムを追加する方法

既存のテーブルにカラムを追加するには、カラム追加用のマイグレーションファイルを作成して、マイグレーションを実行します。

マイグレーションファイルを作成するにはターミナルで次のコマンドを実行します。

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つの方法があります。

カラムをユニーク(一意)にする方法
  1. マイグレーションファイルで一意になるカラムを指定する(DB側で制約)
    • unique: true
  2. モデルで一意になるカラムを指定する(アプリケーション側で制約)
    • uniqueness: true

基本的にはどちらか一方でユニークの制約をかけていれば問題ありませんが、全く同じタイミングで同時にアクセスがあった場合に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の処理を生成するものではありません。

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) ====================


以上で完了です。

タイトルとURLをコピーしました