結論
test/fixture/テーブル名.yml
のデータが被っていることが原因でした。
User
モデルのemail
カラムにUnique制約を後から付与した結果、テストデータのemail
カラムの値が被っていたため制約に違反しているというエラーが出ていたみたいです。
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
email: MyString
two:
name: MyString
+ email: MyString2 # 被ってなければエラーは出ない
- email: MyString
参考
↑この回答がなければ多分詰んでました。
やったこと
何をやったら上のエラーが発生するのか、また私が発生した時の状況を書いておきます。
書いてる人が初心者のため、全ての情報を鵜呑みにするのはやめたほうがいいです。
貼ってあるリンクのページを見るか、自分で調べることを推奨します。
環境
- MacOS Sonoma 14.1
- 開発環境: Docker 25.0.3
- Dockerイメージ: Ruby
- コンテナの起動方法: Dev containersを使用
- Rubyのバージョン: 3.3.0
- Railsのバージョン: 7.1.3.2
- データベース: SQLite(Dockerイメージについているものを使用)
Userモデルを作成
User
モデルは、Ruby on Railsの勉強のために作成しているアプリケーションで、とりあえず生成することにしたモデルです。
今のところはユーザー登録に使う予定です。
# Userモデルを生成
rails g model User name:string email:string
カラムの詳細はこんな感じです。
カラム名 | 型 | 備考 | 例 |
---|---|---|---|
name |
string | ユーザー名、被ってもいい | nanasi |
email |
string | メールアドレス、ユニークにしたい | nanasi@example.com |
すると、以下のファイルが生成されます。
app/models/user.rb
db/migrate/現在の日時_create_users.rb
test/fixture/users.yml
test/models
users.yml
この中で今回関係あるのはtest/fixture/users.yml
のみです。
このファイルは、簡単に言えばテストデータを書くことができるファイルです。
こんな感じのが生成されると思います。
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
email: MyString
two:
name: MyString
email: MyString
細かくは読んでいないのですが、この記事がわかりやすかったので、詳しい説明はこちらを見てください。
マイグレート
マイグレートというのは、マイグレーションファイルの内容をデータベースとモデルに適用させるみたいなやつです。
以下の記事にマイグレーションファイルを使ってカラムを追加/削除する方法が書かれています。
詳しく知りたい方はこちらを見てください。
一度ここでマイグレートしておきます。
rails db:migrate
この際db/schema.rb
が生成されると思います。
インデックスを作成
インデックスとは、簡単に言えばデータの検索が早くなるやつです。
また、ユニーク制約を設けるときにも作ります。
下のページがわかりやすかったので、詳しく知りたい方はこちらを見てください。
インデックスの作成方法は、簡単に言えばこうです。
- マイグレーションファイルを作成
- マイグレーションファイルを、インデックスを追加する内容になるよう編集
- データベースをマイグレートする
マイグレーションファイルを作成
以下のコマンドを実行します。
rails g migration User
すると、db/migrate
に以下のファイルが作られます。
class Users < ActiveRecord::Migration[7.1]
def change
end
end
これがマイグレーションファイルです。
このファイルを変更し、マイグレートすることで、モデルとDBに変更を加えることができます。
マイグレーションファイルを編集
インデックスの追加には、add_index
メソッドを使います。
引数にカラム名のシンボルを入れると、そのカラムにインデックスが追加されます。
ファイルを以下のように編集します。
class Users < ActiveRecord::Migration[7.1]
def change
+ add_index :users, :email, unique: true
end
end
unique: true
という箇所は、email
カラムにユニーク制約を設ける設定です。
ちなみに:users
という箇所はよくわかってません。
データベースをマイグレート
先ほどと同じく、以下のコマンドでできます。
rails db:migrate
テストを実行、エラーが発生
もともとこのアプリケーションには、User
モデルとは全く関係のないコントローラーがあり、そちらの動作を確認するためのテストが書かれていました。
動作確認のため、以下のコマンドでテストを実行しました。
rails t
すると、以下のエラーが発生しました。
ERROR StaticPagesControllerTest#テスト名1 (0.15s)
Minitest::UnexpectedError: ActiveRecord::RecordNotUnique: RuntimeError: UNIQUE constraint failed: users.email
# スタックは略
ERROR StaticPagesControllerTest#テスト名2 (0.15s)
Minitest::UnexpectedError: ActiveRecord::RecordNotUnique: RuntimeError: UNIQUE constraint failed: users.email
# スタックは略
# これがテストの個数分(7個)続く
エラー内容
エラーメッセージにはUNIQUE constraint failed: users.email
と書かれています。
これを日本語にすると「users.email
がユニーク制約に違反している」です(多分)。
確かにemail
カラムにはユニーク制約を追加しましたが、一体どういうことでしょうか?
データベースの内容を見る
とりあえず、データベースの内容を見てみます。
dbconsole
を使ってデータベースにSELECT
クエリを叩いてみました。
rails dbconosle
sqlite>
SELECT * FROM users;
1|test|test@sample.com|日付|日付
.quit
>dockerのプロンプト
見ての通り、そもそもデータが1つしかないので、ユニーク制約違反など起こしようがありません。
サーバーを建ててアクセスしてみる
テストではない場所ならどうなのかということで、開発環境で試してみました。
私の場合、あらかじめユーザー一覧をJSON形式で取得するAPIを用意しているので、特に準備は不要でした。
rails s
コマンドでサーバーを立ち上げます。
そして、/users
(前述のAPI)にアクセスします。
ステータスコードは200
で、以下のようなレスポンスが返ってきました。
[
{
"id": 1,
"name": "admin",
"email": "test@sample.com",
"created_at": "日付",
"updated_at": "日付"
}
]
しっかり取得できているみたいです。
データをDBに追加してみる
ここで、本当にユニーク制約が動作しているのか見るため、データをDBに追加してみました。
irb
>irb
user = User.new
user.name = '被ってないよ'
user.email = 'test@sample.com' # 被ってる
user.save
# なんか失敗してる
email
が被っているデータは追加できないみたいです。
ここは意図した通りに動作しています。
エラー文をコピペして調べる
ここでやっとエラー文をコピペして調べることにしました。
我ながら遅いですね。
そして、先ほども書いた以下のStackOverflowの質問を見つけました。
どうやらtest/fixture/users.yml
を編集するといいみたいです。
このファイルはなんなのか?という疑問を持ったため調べたら、先ほども書いたこの記事に出会いました。
users.ymlを修正する
もとのusers.yml
は、以下のようにemail
カラムの値がどちらもMyString
になっていました。
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
email: MyString
two:
name: MyString
email: MyString
おそらく、テストを実行しようとした際に上のテストデータをDBに登録しようとし、その結果ユニーク制約に違反してしまってエラーが出た、ということだと思います。
これは詰まるって...
ということで、どうせならテストデータ風にusers.yml
を編集してみました。
最終的に出来上がったのがこちらです。
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# ここの値を被せるとキー制約に違反する
normal:
name: nanasi
email: nanasi@sample.com
error:
name: ''
email: 'not_email'
テストを実行する
ということで、テストを実行します。
rails t
Running 7 tests in a single process (parallelization threshold is 50)
Started with run options --seed 42455
7/7: [==================================================================================] 100% Time: 00:00:00, Time: 00:00:00
Finished in 0.39507s
7 tests, 11 assertions, 0 failures, 0 errors, 0 skips
ハイライトでは赤くなってますが(実際には緑)、無事にテストが成功しています。
よかったー...