#要点
- 初心者がアプリを作るときの参考に
- dependent: :destroy を用いて外部キーの削除制限を外す
- 下記のエラー(Mysql2エラー)を解消する方法
ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`-中間テーブル名-`, CONSTRAINT `fk_rails_~~~~~` FOREIGN KEY (`-商品のテーブル名-_id`) REFERENCES `-商品のテーブル名-` (`id`))):
#はじめに
メルカリのようなフリマアプリを作成中で、Formオブジェクトを用いて商品にタグ付けして出品できる機能を実装する所まで行いました
各モデルとコントローラーは以下のようになります
- Item/商品
- Tag/タグ
- TagItemRelation/商品とタグの中間テーブル
- TagsItem/ ItemとTagを同時に保存するためのFormオブジェクト
class Item < ApplicationRecord
has_many :tag_item_relations
has_many :tags, through: :tag_item_relations
end
class Tag < ApplicationRecord
has_many :tag_item_relations
has_many :items, through: :tag_item_relations
validates :tag_name, uniqueness: true
end
class TagItemRelation < ApplicationRecord
belongs_to :item
belongs_to :tag
end
class TagsItem
include ActiveModel::Model
attr_accessor :item_name,
:tag_name
with_options presence: true do
validates :item_name
end
def save
item = Item.create(item_name: item_name)
tag = Tag.where(tag_name: tag_name).first_or_initialize
tag.save
TagItemRelation.create(item_id: item.id, tag_id: tag.id)
end
end
コントローラー
class ItemsController < ApplicationController
def index
@items = Item.all.order('created_at ASC')
end
def new
@item = TagsItem.new
end
def create
@item = TagsItem.new(items_params)
if @item.valid?
@item.save
redirect_to root_path
else
render :new
end
end
private
def items_params
params.require(:tags_item).permit(
:item_name,
:tag_name
)
end
end
その後商品閲覧機能と
問題の__商品削除機能__を実装した
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :destroy]
### 中略
def show
@tags_item = TagItemRelation.find(params[:id])
end
def destroy
if current_user.id == @item.user_id
@item.destroy
redirect_to root_path
else
render :show
end
end
private
### 中略
def set_item
@item = Item.find(params[:id])
@tag = Tag.find(params[:id])
end
end
しかし実際に出品した商品を削除してみると、、、
ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`tag_item_relations`, CONSTRAINT `fk_rails_~~~~~~` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`))):
のエラーが出てしまい、商品の削除ができませんでした
#調べたこと
エラー内容をよく読んでみると、商品idは中間テーブルの__外部キー(foreign key)__に含まれて
デリートやアップデートができないよ!と書かれているのがわかります
ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`-アプリ名-_development`.`tag_item_relations`, CONSTRAINT `fk_rails_~~~~~~` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`))):
そこで外部キーについて調べてみると、、、
外部キー(外部制約キーとも)は、よくマイグレーションファイルなどに書いてるデータベースのキーで
データベースの特定の項目に好き勝手な値を入れることを防ぎ、外部の項目から選んで入れる制約を持ち
主に外部キーのテーブルから主キーのテーブルのデータに対して、変更を制限__するものだそうです
今回の場合外部キーのテーブルは__tag_item_relations、主キーのテーブルは__item__になることが分かりますね
外部キーの設定変更
今回のエラーの原因はマイグレーションファイルに記載した外部キー(foreign_key: trueのやつ)でした
ですがマイグレーションファイルごと書き換えてしまうと別の不具合が起きてしまう恐れがあります
今回はdestoryで外部キーとして設定している項目を削除させるように、__dependent: :destroy__を設定しようと思います
この簡単な設定により不具合なく商品の削除ができるようになります
class Item < ApplicationRecord
has_many :tag_item_relations, foreign_key: :item_id, dependent: :destroy
has_many :tags, through: :tag_item_relations
end
#参考にしたサイト
外部キー (foreign key)とは
#関連記事
Formオブジェクトを用いて作成したデータを、編集・更新する方法 ←次回記事、編集更新はこちら