Railsでマイグレーション時に発生するよくあるエラーとその対処法


筆者の本職は Web アプリケーションエンジニアですが、プログラミングスクールの講師として、Ruby on Rails のコースを担当しています。

そこで、受講生からよく聞かれる、Rails アプリケーションの開発中に遭遇するマイグレーションエラーについて、具体例を挙げて解説します。

## 1. `ActiveRecord::PendingMigrationError` - マイグレーションが保留状態のときの解決策

**問題のある状況：**
新しいマイグレーションファイルを作成したが、`rails db:migrate`を実行していない場合に発生します。

```bash
$ rails s
ActiveRecord::PendingMigrationError: Migrations are pending. To resolve this issue, run:
  bin/rails db:migrate
```

**解決方法：**
マイグレーションが保留中であるため、以下のコマンドを実行してマイグレーションを適用します。

```bash
$ rails db:migrate
```

## 2. `ActiveRecord::StatementInvalid: Could not find table` - テーブルが存在しないエラーの原因と対処

**問題のあるマイグレーションファイル：**

```ruby
class AddAgeToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :age, :integer
  end
end
```

上記のマイグレーションを適用しようとすると、`users`テーブルがまだ存在しない場合にエラーが発生します。

```bash
ActiveRecord::StatementInvalid: Could not find table 'users'
```

**修正方法：**
`users`テーブルを作成するマイグレーションが適用されていない場合、最初にテーブルを作成するマイグレーションを適用する必要があります。

```bash
$ rails db:migrate
```

もしくは、テーブルを作成するマイグレーションが存在しない場合、テーブル作成のマイグレーションを追加します。

```ruby
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age

      t.timestamps
    end
  end
end
```

## 3. `NoMethodError: undefined method 'change'` - マイグレーションファイルでメソッドが見つからないときの原因と解決策

**問題のあるマイグレーションファイル：**

```ruby
class RenameColumnInUsers < ActiveRecord::Migration[6.0]
  def changes # Typo: 'change' が 'changes' になっている
    rename_column :users, :fullname, :name
  end
end
```

`change`メソッド名が間違っているため、このエラーが発生します。

```bash
NoMethodError: undefined method 'changes'
```

**修正したマイグレーションファイル：**

```ruby
class RenameColumnInUsers < ActiveRecord::Migration[6.0]
  def change
    rename_column :users, :fullname, :name
  end
end
```

`def change` と正しいメソッド名に修正することで、エラーが解消されます。

## 4. `Mysql2::Error: Table already exists` - テーブルが既に存在するエラーの対処方法

**問題のあるマイグレーションファイル：**

```ruby
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age

      t.timestamps
    end
  end
end
```

上記マイグレーションを適用しようとすると、`users`テーブルが既に存在する場合にエラーが発生します。

```bash
Mysql2::Error: Table 'users' already exists
```

**修正したマイグレーションファイル：**

既存テーブルを確認してから、テーブル作成を実行するように変更します。

```ruby
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    unless table_exists?(:users)
      create_table :users do |t|
        t.string :name
        t.integer :age

        t.timestamps
      end
    end
  end
end
```

## 5. `PG::DuplicateColumn` - カラム名が重複している場合のエラー修正方法

**問題のあるマイグレーションファイル：**

```ruby
class AddAgeToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :age, :integer
    add_column :users, :age, :integer # 重複している
  end
end
```

`age` カラムが既に存在するにもかかわらず、再度追加しようとするとエラーが発生します。

```bash
PG::DuplicateColumn: ERROR:  column "age" of relation "users" already exists
```

**修正したマイグレーションファイル：**

カラムが既に存在しないかどうかを確認してから追加するように修正します。

```ruby
class AddAgeToUsers < ActiveRecord::Migration[6.0]
  def change
    unless column_exists?(:users, :age)
      add_column :users, :age, :integer
    end
  end
end
```

## 6. `PG::UndefinedColumn` - カラムが見つからないエラーの原因と解決策

**問題のあるクエリ：**

```ruby
User.select(:nonexistent_column).first
```

存在しないカラム `nonexistent_column` を参照しようとして、エラーが発生します。

```bash
PG::UndefinedColumn: ERROR:  column "nonexistent_column" does not exist
```

**修正方法：**

正しいカラム名を指定します。もしカラムが新しく追加された場合、そのカラムをマイグレーションで作成していることを確認してください。

```ruby
User.select(:name).first
```

## 7. `Mysql2::Error: Cannot add a NOT NULL column with default value NULL` - NULL 制約のエラー対応

**問題のあるマイグレーションファイル：**

```ruby
class AddEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :email, :string, null: false
  end
end
```

すでにデータがある `users` テーブルに `null: false` の制約を持つカラムを追加しようとすると、既存のレコードに NULL 値が設定されてしまい、エラーが発生します。

```bash
Mysql2::Error: Cannot add a NOT NULL column with default value NULL
```

**修正したマイグレーションファイル：**

デフォルト値を設定するか、一時的に NULL 値を許可してからデータを埋め、その後に制約を追加します。

```ruby
class AddEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :email, :string, default: 'example@example.com', null: false
  end
end
```

## 8. `PG::NotNullViolation` - NOT NULL 制約が原因で起こるエラーへの対処法

**問題のあるクエリ：**

```ruby
User.create!(name: nil)
```

`name` カラムが `null: false` 制約を持っているにもかかわらず、`nil` を挿入しようとするとエラーが発生します。

```bash
PG::NotNullViolation: ERROR:  null value in column "name" violates not-null constraint
```

**解決方法：**

必須カラムに対して適切な値を指定するか、マイグレーションでデフォルト値を設定することで解決します。

```ruby
User.create!(name: 'John Doe')
```

## 9. `PG::UniqueViolation` - 一意制約違反エラーが発生したときの解決策

**問題のあるマイグレーションファイル：**

```ruby
class AddEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :email, :string, unique: true
  end
end
```

すでに重複するデータがある場合、このマイグレーションを実行するとエラーが発生します。

```bash
PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint
```

**修正したマイグレーションファイル：**

既存データに対してユニーク制約を満たすように処理してから制約

を追加します。

```ruby
class AddEmailToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :email, :string

    # 重複を避けるために一意な値に修正
    User.find_each do |user|
      user.update(email: "user#{user.id}@example.com") if user.email.nil?
    end

    add_index :users, :email, unique: true
  end
end
```

## 10. `Mysql2::Error: Foreign key constraint fails` - 外部キー制約エラーのトラブルシューティング

**問題のあるマイグレーションファイル：**

```ruby
class AddForeignKeyToOrders < ActiveRecord::Migration[6.0]
  def change
    add_reference :orders, :user, foreign_key: true
  end
end
```

関連する `users` テーブルに存在しない `user_id` を持つレコードが `orders` テーブルに存在する場合、このエラーが発生します。

```bash
Mysql2::Error: Foreign key constraint fails
```

**修正したマイグレーションファイル：**

外部キー制約を追加する前に、`orders` テーブルの不整合なデータを修正します。

```ruby
Order.where.not(user_id: User.select(:id)).delete_all
```

その後、外部キー制約を追加します。

```ruby
class AddForeignKeyToOrders < ActiveRecord::Migration[6.0]
  def change
    add_reference :orders, :user, foreign_key: true
  end
end
```

## 11. `SQLite3::ConstraintException: UNIQUE constraint failed` - 重複エントリによるエラーの修正

**問題のあるクエリ：**

```ruby
User.create!(email: 'example@example.com')
User.create!(email: 'example@example.com')
```

一意制約を持つ `email` カラムに重複したデータを挿入しようとするとエラーが発生します。

```bash
SQLite3::ConstraintException: UNIQUE constraint failed: users.email
```

**修正方法：**

データを挿入する前に、既に存在するかどうかを確認して処理します。

```ruby
User.find_or_create_by!(email: 'example@example.com')
```

## 12. `ActiveRecord::IrreversibleMigration` - ロールバックできないマイグレーションの対応方法

**問題のあるマイグレーションファイル：**

```ruby
class RemoveEmailFromUsers < ActiveRecord::Migration[6.0]
  def change
    remove_column :users, :email
  end
end
```

削除したカラムの情報はロールバックできないため、以下のエラーが発生します。

```bash
ActiveRecord::IrreversibleMigration
```

**修正したマイグレーションファイル：**

削除操作に対して `up` と `down` メソッドを定義して、ロールバック時にカラムを再作成する処理を追加します。

```ruby
class RemoveEmailFromUsers < ActiveRecord::Migration[6.0]
  def up
    remove_column :users, :email
  end

  def down
    add_column :users, :email, :string
  end
end
```