質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

解決済

2回答

1249閲覧

Ruby on Rails 外部キーの制約によるINSERTエラー

jack20xx

総合スコア45

SQLite

SQLiteはリレーショナルデータベース管理システムの1つで、サーバーではなくライブラリとして使用されている。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2022/03/18 03:14

前提

Ruby on Railsで映画のレビューサイトを作っています。
映画のIDを取得して、レビューを投稿しようとしたところ、以下のエラーメッセージが発生しました。

外部キーの制約の問題だと思われますが、同内容のエラーが各種サイトでは見つからず、対応方法が分からなかったため、質問させていただきます。

実現したいこと

  • 外部キーの制約の問題を解消し、レビューを投稿できるようにしたい

発生している問題・エラーメッセージ

ActiveRecord::InvalidForeignKey in CommentsController#create SQLite3::ConstraintException: FOREIGN KEY constraint failed: INSERT INTO "comments" ("content", "user_id", "movie_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)

該当のソースコード

エラーと関係していると思われるソースコードや必要情報を記載いたしました。

1. comments_controller
2. schema
3. 外部キーの制約に関するマイグレーション
4. commentモデル
5. userモデル
6. movieモデル
ーーーーーーーーーーーーーーーーーーーーー

1. comments_controller

class CommentsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy def create puts params @comment = Comment.new() @comment.content = params["comment"][:content] @comment.user_id = current_user.id @comment.movie_id = params["comment"][:movie_id] # @comment = Comment.new(comment_params) # @comment = current_user.comments.build(comment_params) if @comment.save flash[:success] = "Comment created!" redirect_to root_url else @feed_items = [] render 'static_pages/home' end end def destroy @comment.destroy flash[:success] = "Comment deleted" redirect_to request.referrer || root_url end private def comment_params params.require(:comment).permit(:content, :user_id) end def correct_user @comment = current_user.comments.find_by(id: params[:id]) redirect_to root_url if @comment.nil? end end

2. schema

ActiveRecord::Schema.define(version: 20220313085506) do create_table "comments", force: :cascade do |t| t.text "content" t.integer "user_id" t.integer "movie_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["movie_id"], name: "index_comments_on_movie_id" t.index ["user_id", "movie_id", "created_at"], name: "index_comments_on_user_id_and_movie_id_and_created_at" t.index ["user_id"], name: "index_comments_on_user_id" end create_table "movies", force: :cascade do |t| t.string "title" t.string "date" t.string "story" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "password_digest" t.string "remember_digest" t.boolean "admin", default: false t.string "activation_digest" t.boolean "activated", default: false t.datetime "activated_at" t.string "reset_digest" t.datetime "reset_sent_at" t.index ["email"], name: "index_users_on_email", unique: true end end

3. 外部キーの制約に関するマイグレーション

class CreateComments < ActiveRecord::Migration[5.1] def change create_table :comments do |t| t.text :content t.references :user, foreign_key: true t.references :movie, foreign_key: true t.timestamps end add_index :comments, [:user_id, :movie_id, :created_at] end end

4. commentモデル

class Comment < ApplicationRecord belongs_to :user belongs_to :movie, optional: true default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 250} end

5. userモデル

class User < ApplicationRecord has_many :comments, dependent: :destroy has_many :movies, through: :comments attr_accessor :remember_token, :activation_token, :reset_token before_save :downcase_email before_create :create_activation_digest validates :name, presence: true, length: { maximum: 50} VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255}, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: true def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end def User.new_token SecureRandom.urlsafe_base64 end def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end def forget update_attribute(:remember_digest, nil) end def authenticated?(attribute, token) digest = send("#{attribute}_digest") return false if digest.nil? BCrypt::Password.new(digest).is_password?(token) end def activate update_columns(activated: true, activated_at: Time.zone.now) end def send_activation_email UserMailer.account_activation(self).deliver_now end def create_reset_digest self.reset_token = User.new_token update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now) end def send_password_reset_email UserMailer.password_reset(self).deliver_now end def password_reset_expired? reset_sent_at < 2.hours.ago end def feed Comment.where("user_id = ?", id) end private def downcase_email self.email.downcase! end def create_activation_digest self.activation_token = User.new_token self.activation_digest = User.digest(activation_token) end end

6. movieモデル

class Movie < ApplicationRecord include HTTParty has_many :comments, dependent: :destroy has_many :users, through: :comments default_options.update(verify: false) default_params api_key: '#実際はコードが入っています', language: "ja-JP" format :json def self.search term base_uri 'https://api.themoviedb.org/3/search/movie' get("", query: { query: term}) end def self.details id #base_uri "https://api.themoviedb.org/3/movie/#{id}" get("https://api.themoviedb.org/3/movie/#{id}", query: {} ) end # def self.popular(page=1) # base_uri 'https://api.themoviedb.org/3/movie/popular' # get("", query: { language: 'ja-JP', region: "JP" }) # end end

コメント

初心者であり、至らないところがありましたら、申し訳ございません。
ソースコードに関しましては、必要なものがありましたら、追記いたしますので、教えていただければ幸いです。

何卒宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2022/03/18 11:19

CommentsController#create はどの画面から呼ばれるんですか? 普通に考えたら、各映画のページにコメント欄があって、そこに書き込んだ時に呼ばれるんだと思いますが、その場合すでに映画は存在してないとおかしいですよね…。
jack20xx

2022/03/18 11:41

hoshi-takanori様 コメントありがとうございます。 現状では、ユーザーページと各映画のページから呼ばれるようになっています。 ユーザーから投稿する場合は、movie_idを必要としないため投稿可能なのですが、映画ページからmovie_idを取得して投稿しようとすると上記のエラーが出ます。 各映画は、映画のAPIからIDを取得しているため表示されるのですが、親テーブルのデータには入っていないため投稿が成功せず、エラーが出ているのではないかと思われます。 よって、APIからどのように親テーブルのデータに映画のIDを結びつけるのかが問題だと思いますが、解決策が見つかっていない状況です。
hoshi-takanori

2022/03/18 11:47

つまり映画テーブルは不要で、movie_id も外部キーにする必要はないってことでは。
hoshi-takanori

2022/03/18 12:08

補足すると、コメントの外部キーをどうするか考える前に、まず映画データの扱い方をどうするかを決める必要があるかと。たとえば、 A. 映画 API の結果を取得したら永続的にデータベースに登録する。これなら movie_id は外部キーにできますが、映画 API の規約に触れるかも? B. 映画の情報が必要になるたびに映画 APi を叩く。この場合、映画テーブルは不要になり、movie_id は外部キーではなくなります。 C. 映画の情報をある程度キャッシュする。この場合も movie_id は外部キーにはできません。
jack20xx

2022/03/18 12:14

コメントありがとうございます。 おっしゃるとおりでした。外部キーを削除したところ、無事に機能しました。 実は、これは同じように映画のAPIを利用した方のソースコードが外部キーによって機能していたので、参考にしていたものでした。 参考元がどのように機能していたかは、改めて考える必要がありそうです。 何週間も、これだけで考えてしまっていたので、大変助かりました。 本当にありがとうございました。
jack20xx

2022/03/18 12:16

補足のほうも、ありがとうございます。 大変分かりやすく、理解できました。 教えていただきました内容から考えるに、外部キーにする必要はおそらくなさそうです。
jack20xx

2022/03/24 09:55

hoshi-takanori様 先日はありがとうございました。 あれから、試行錯誤しておりますが、外部キーの件で新たに前回に関する点で気になることがあります。 質問させていただくことはできますでしょうか?
jack20xx

2022/03/24 12:15

お忙しいところ、ご返信ありがとうございます。 前回のお話から、外部キーは要らないものと判断し、削除しました。 それにより投稿自体は可能となり、ユーザーページに表示させることはできました。 しかし、各映画ページにレビューを表示させようとしたところ データベースに映画の情報が入っていないせいか、うまくいきませんでした。 上記のことから、映画の情報は先日お話を頂いたような、 取得した情報を永続的にデータベースに残す必要があると考えました。 このような仮説は間違っていますでしょうか?
hoshi-takanori

2022/03/24 14:53

削除って、comment モデルの belongs_to :movie, optional: true を丸ごと消したってことでしょうか? その場合、映画ページに表示するコメントを検索する方法がなくなりますよね…。 ので、代わりに movie_id 属性を追加してどの映画に対するコメントかを記録する必要があるかと。また、その movie_id が一意になってる (同じ映画に対して毎回同じ id が振られる) こともご確認ください。
jack20xx

2022/03/24 23:46

説明不足で申し訳ありません。 消させていただいたのは、外部キーの制約関するマイグレーションの部分の t.references :movie, foreign_key: true です。削除したことで、投稿自体はエラーを出さずに行うことができました。 映画ページの検索などは問題なく行えるのですが、レビューの投稿を各映画ページに表示させることができないという状態です。 movie_idもレビューを投稿すると同じ映画に対して同じidが振られるのも確認できているため、一意になっていると考えられます。 そのため、各映画ページに投稿を表示させるためには、 映画情報をデータベースに登録する必要性を考えた次第です。 またはviewページのソースコードに問題があるのでしょうか?
hoshi-takanori

2022/03/25 00:03 編集

belongs_to :movie, optional: true って movie オブジェクトが存在することが前提だと思うので、これを使うなら movie オブジェクトを作る必要があるでしょうし、movie オブジェクトを作らないなら、これは消して、その代わり movie_id を追加する必要があるかと…。
jack20xx

2022/03/25 00:18

ありがとうございます。 理解いたしました。 その場合、movie_idを追加するには、どのようなコードの書き方が よろしいでしょうか?
guest

回答2

0

自己解決

試行錯誤した結果、親テーブルにデータを追加することができたため、
無事に自分の求める動作をするようになりました。

質問をともに考えてくださった方々、ありがとうございました。

投稿2022/04/03 04:44

jack20xx

総合スコア45

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

SQLite3::ConstraintException: FOREIGN KEY constraint failed: INSERT INTO "comments" ("content", "user_id", "movie_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)

外部キーはあらかじめ親テーブル(今回はuserとmovie)にデータが存在している状態で、子テーブル(今回はcomments)をINSERTしなければなりません。

create_table :comments do |t| t.text :content t.references :user, foreign_key: true t.references :movie, foreign_key: true

今回は親テーブルにデータが存在しないのに子テーブルをINSERTしたのでエラーになった(父無し子は認めない)だけです。

投稿2022/03/18 03:42

Orlofsky

総合スコア16415

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jack20xx

2022/03/18 04:39

Orlofsky様 素早いご回答ありがとうございます。 追記の情報ですみませんが、今回の場合、"movie_id"を取得しない場合の投稿は成功します。つまり、親テーブルである"movie"に"movie_id"が存在しないため、 "movie_id"を取得する場合の投稿は成功しないという解釈でよろしいでしょうか? また親テーブルにデータを入れるにはAPIを利用していても可能でしょうか? 今回の場合、外部キーを削除することでうまく機能するかどうかも考えたのですが、その辺りの対応が分からず、調べがついていない状態です。
Orlofsky

2022/03/25 03:14

>親テーブルである"movie"に"movie_id"が存在しないため、 "movie_id"を取得する場合の投稿は成功しないという解釈でよろしいでしょうか? 外部キーがAPIであろうとなかろうと、そういう意味のエラーメッセージが出ているのでは?
jack20xx

2022/03/26 06:15

ご返信ありがとうございます。 あれから調べて理解いたしました。 外部キーを削除することで投稿自体はできるようになりましたが、各映画のページにレビューを表示するには、親テーブルにデータを入れる必要があるようで、APIを利用してそれをどのように行うのか調べているところです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問