環境
Ruby:3.2.2
Rails:7.1.5
期待していた動作
・ログインしたらタスク一覧を表示してほしい
期待する動作までの処理の概要
・ログイン中のユーザーからアソシエーションを利用してタスクを取得してビューに渡す
def index
@tasks = current_user.tasks.all
end
症状
・ActiveRecord::StatementInvalid(SQL実行時のエラー)が出ていた
・tasksテーブルにuser_id:というカラムが存在しないようだ
SQLite3::SQLException: no such column: tasks.user_id:
SELECT "tasks".* FROM "tasks" WHERE "tasks"."user_id" = ?
・外部キー:user_idが存在するかをdbconsoleで確認したが存在しないようだった
sqlite> .schema tasks
CREATE TABLE IF NOT EXISTS "tasks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" text NOT NULL, "description" text DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
推察
・ひとまず原因は外部キーのカラムが存在しないから臭い
調査
https://railsguides.jp/active_record_migrations.html#%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC
調査結果の要約
・アソシエーションを利用するためにはDBで外部キー制約を持ったカラムが必要。
・add_referenceを利用するだけでは外部キー制約は追加されない。
・foreign_key:trueを追加することで外部キー制約が張られる。
よくない実装
add_reference :tasks, :user
正しい実装
add_reference :tasks, :user, foreign_key: true
原因
・tasksテーブルに外部キー制約を持ったuser_idのカラムが存在していなかった。
・(カラム自体が存在しなかったのは謎だがそういうこともあると流しておく)
解決策
add_reference :tasks, :user, foreign_key: true
add_reference :tasks, :user, foreign_key: true
うまくいった
CREATE TABLE IF NOT EXISTS "tasks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" text NOT NULL, "description" text DEFAULT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_id" integer DEFAULT NULL, CONSTRAINT "fk_rails_4d2a9e4d7e"
FOREIGN KEY ("user_id")
REFERENCES "users" ("id")
);
foreign_key: trueを指定しないとカラム自体追加できない可能性があるようだ(原因は謎)
抽象化・一般化・重要なポイント
・アソシエーションの利用には外部キー制約を持ったカラムが必要である
・addreferenceだけでなくforeign_key:trueを指定してマイグレーションを行う必要がある