LoginSignup
2
0

More than 1 year has passed since last update.

テーブルに後からユニーク制約(一意性)を追加する

Posted at

やりたいこと

User毎に登録Wordを独立させて、User毎に登録Wordのユニーク制約を適用したい。

  • 最初に作成したWordテーブルはユニーク制約を用いて同じWordの登録を不可にしている
  • 新たにUserテーブルを作成した
  • このままではUser全体でWordにユニーク制約がかかってしま(例:User1とUser2でそれぞれtestという単語を登録できない)

やったこと

下記の記事を参考にして、新たにマイグレーションファイルを作成しadd_indexの部分を記述した。(add_referenceは外部キー制約を追加するための記述で、ここでは説明を省く)
uniqueness: scope を使ったユニーク制約方法の解説

class AddUserToWords < ActiveRecord::Migration[6.1]
  def change
    add_reference :words, :user, null: false, foreign_key: true
    add_index :words, %i[word user_id], unique: true
  end
end

ちなみに、最初に作成したWordのマイグレーションファイルには以下のようにユニーク制約が設けてある。

class CreateWords < ActiveRecord::Migration[6.1]
  def change
    create_table :words do |t|
      t.string :word, null: false, limit: 100
      t.index :word, unique: true

      t.timestamps
    end
  end
end

この状態でrails db:migrateを実行すると、schema.rbは以下のようになる。

schema.rb
  create_table "words", force: :cascade do |t|
    t.string "word", limit: 100, null: false

    t.index ["word", "user_id"], name: "index_words_on_word_and_user_id", unique: true
    t.index ["word"], name: "index_words_on_word", unique: true
  end

  add_foreign_key "words", "users"
end

エラー内容

上記の状態でUser1User2で同じtestという単語を登録しようとすると下記のエラーが発生した。

ActiveRecord::RecordNotUnique in WordsController#create
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_words_on_word"
DETAIL: Key (word)=(test) already exists.

エラーの原因

エラー文に書いてあるとおり、

index_words_on_wordの部分が悪さをしているので、元々のWordのマイグレーションファイルに記述してあるt.index :word, unique: trueが原因。

解決策

元々のWordのマイグレーションファイルに記述してあるt.index :word, unique: trueを消去する。
マイグレーションファイルに下記を追記してrails db:migrate:resetを実行した。

class AddUserToWords < ActiveRecord::Migration[6.1]
  def change
    add_reference :words, :user, null: false, foreign_key: true
    add_index :words, %i[word user_id], unique: true
    remove_index :words, :word
  end
end

無事にUser毎に登録Wordを独立させて、User毎に登録Wordのユニーク制約を適用することができた。

学んだこと

これまでデータベース周りをいじることが少なかったため、理解すると簡単なことだったが解決までに時間を要してしまった。
今回の経験を通じてデータベース周りの理解が深まった。

補足

今回はデータベースをリセットしても問題ないため、rails db:migrate:resetを実行した。
リセットしたら困る場合は新たにマイグレーションファイルを作成し、そこにremove_indexを記述し、rails db:migrateで対応する必要があると考える。

また、リセットして構わないのであれば、最初のWordマイグレーションファイルからt.index :word, unique: trueを削除するのでも良かったかもしれない。

ちなみにモデル側でも下記のようにscopeを指定してユニーク制約を設けている。

word.rb
class Word < ApplicationRecord
  belongs_to :user
  validates :word, presence: true, uniqueness: { scope: :user_id }, length: { maximum: 100 }
end

データベースとモデルの両方でユニーク制約をつける意味については、Railsチュートリアルの説明が理解しやすかったです。

参考

uniqueness: scope を使ったユニーク制約方法の解説

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0