LoginSignup
2
0

More than 1 year has passed since last update.

Formオブジェクトを用いて作成したデータを、特定のデータのみ削除する方法

Last updated at Posted at 2021-01-13

要点

  • 初心者がアプリを作るときの参考に
  • 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オブジェクト
/app/model/item.rb
class Item < ApplicationRecord
  has_many :tag_item_relations
  has_many :tags, through: :tag_item_relations
end
/app/model/tag.rb
class Tag < ApplicationRecord
  has_many :tag_item_relations
  has_many :items, through: :tag_item_relations
  validates :tag_name, uniqueness: true
end
/app/model/tag_item_relation.rb
class TagItemRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end
/app/form/tags_item.rb
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

コントローラー

/app/controller/items_controller.rb
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

その後商品閲覧機能と
問題の商品削除機能を実装した

/app/controller/items_controller.rb
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を設定しようと思います
この簡単な設定により不具合なく商品の削除ができるようになります

/app/model/item.rb
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オブジェクトを用いて作成したデータを、編集・更新する方法 ←次回記事、編集更新はこちら

外部キー制約でハマったので(rails)

外部キーを持つデータのdestroyを行うための設定

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