【Rails】〇〇 must exists? フォームが送信できない?【merge】

本記事はこんな悩みを持った方のために書いています。

  • フォーム送信時に、〇〇 must existsというエラーが出た人
  • パラメーターを渡しているはずなのに、送られない人
  • 送信フォームに必要事項を入力したのに、エラーが発生する人
  • paramsの中身について知りたい人

今回はフォームに必要事項を記入したのに、「〇〇 must exists」というエラーが発生して、フォームを送信できない問題について解説していきます。

またややこしい「ストロングパラメーター」「ハッシュ」についても触れているのでぜひ参考にしてくださいね!

まずは完成形を確認しよう

完成系はフォームに文字を入力して、保存ボタンをクリックすると、その内容がView側で表示されるというものです。

①送信フォームを用意して、必要事項を入力&保存ボタンをクリック

フォームに文字を入力して保存をクリック

②送信した内容がViewで表示される

フォームの内容がViewに表示される

不具合を確認してみよう

では今回の不具合を確認してみましょう。

①送信フォームを用意して、必要事項を入力&保存ボタンをクリック
*画像では文字を入力していませんが、実際は必要事項を入力します。

②User must existというエラーが発生 & 内容を保存できない

〇〇 must existsの原因を特定しよう

今回発生した「〇〇 must exists」というエラーの原因を特定してみましょう。

可能性① アソシエーションの問題?→結論:問題なし

まずActiverecord上UserモデルとTaskモデルが紐づいていないのかもしれないと考えました。つまりUserとTaskの「アソシエーション」ができていないということです。

モデルのUserモデルとTaskモデルでアソシエーションを確認してみます。

user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :boards, dependent: :destroy
  has_many :tasks, dependent: :destroy
  has_one :profile, dependent: :destroy


「User has many tasks」で関係性が作られているので、問題ありませんね。
ちなみに「User has many tasks」は「1人のユーザーは複数のタスクを投稿できる」という意味になります。


次にタスクモデルでアソシエーションを確認していきます。

class Task < ApplicationRecord
    validates :name, presence: true
    validates :description, presence: true
    belongs_to :user
    belongs_to :board
end

ここでも「Task belongs_to User」となっているので、ユーザーとタスクのアソシエーションが正しくできています。よって、アソシエーションの問題ではなさそうだなと分かりました。

可能性② データベースの問題?→結論:問題なし

次に可能性として挙げられるのは、「データベース」です。

例えば、Taskモデルにuser_idみたいなキー(外部キー)が無いなどの可能性が考えられます。現状のモデルの状態を確認するには、dbフォルダにあるschema.rbを確認します。

tasksテーブル

  create_table "tasks", force: :cascade do |t|
    t.bigint "user_id", null: false
    t.string "name", null: false
    t.text "description", null: false
    t.date "deadline"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.bigint "board_id"
    t.index ["board_id"], name: "index_tasks_on_board_id"
    t.index ["user_id"], name: "index_tasks_on_user_id"
  end

Taskテーブルに「user_id」が存在しているので、TaskからUserの情報を引き出せる状態になっいてます。

よって、データベースにも問題ないことが分かりました。

可能性③ コントローラーの問題?→結論:問題あり!

最後はコントローラーをみていきます。コントローラーを見る理由は、「ストロングパラメーター」で、Userの情報を渡せていないかも?と予想したからです。

ではフォームの送信を担うCreateアクション内にデバッガーを仕込みます。デバッガーは「prybyebug」でも「byebug」でもなんでもOKです。

今回はpyebyebugを使って、デバッグしていきます。

class TasksController < ApplicationController
    before_action :authenticate_user!

    def new
      @board = Board.find(params[:board_id])
      @task = @board.tasks.build()
    end

    def create
      @board = Board.find(params[:board_id])
      @task = @board.tasks.build(task_params)

#prybugを挿入
  binding.pry

      if @task.save
        redirect_to board_path(@board), notice: 'コメントを追加'
      else
        flash.now[:error] = '更新できませんでした'
        render :new
      end
    end

    private
    def task_params
        params.require(:task).permit(:name,:description)
    end
end

これでフォームに記載した情報がちゃんと保存できているのか確認できます。
次にprybugをターミナルで使っていきます。

エラーの解決編① paramsの内容を確認しよう

まずはターミナルで「params」と入力します。paramsの中には、フォームによって送られてきた情報が格納されています。

paramsメソッド実行

[1] pry(#<TasksController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"QQCLwksQgQKX3VW5BHmfno7rnWnWPCZ0mwpJblRra9pph6fFL3hLtyJkiFKTRTp9ipjKAZpXvun1qqvcIEhSZA=="
, "task"=><ActionController::Parameters {"name"=>"エラー", "description"=>"エラー"} permitted: false>, 
"commit"=>"保存", "controller"=>"tasks", "action"=>"create", "board_id"=>"14"} permitted: false>

形が少しややこしいですが、重要なのはparamsは{}(ハッシュ)の形になっているということです。

ハッシュは次のように、「キー」と「バリュー」の2つの要素で構成されています。ここでは、nameやageが「キー」で’Taro’や25が「バリュー」となっています。

(ハッシュの例)

Taro = {name: ‘ Taro’, age: 25, height: 175, weight: 60 }

John = {name: ‘ John’, age: 30, height: 182, weight: 70 }


もしハッシュについて自信がない方は、下記記事を参考にしてくださいね。

【Ruby】ハッシュのシンボルについて解説【初心者向け】


では、本題に戻りましょう。
ハッシュの中身をシンプルに分解すると、こんな感じです。

{params:{
  "authenticity_token"=>{キー:バリュー},
  "task"=>{キー:バリュー,キー:バリュー,....}
}}

では再度paramsの中身を分かりやすいように整理してみましょう。
*不要な箇所は削除・省略しています。

{params => {
  "authenticity_token"=>"(省略)==",
  "task"=> {"name"=>"エラー",
  "description"=>"エラー"},
  "commit"=>"保存",
  "controller"=>"tasks",
  "action"=>"create",
  "board_id"=>"14"}
}

このように整理するとparamsがただのハッシュであることが分かりました。

エラーの解決編② @taskの内容を確認しよう

次に@taskの情報を確認してみましょう。なぜ@taskが急に出てきたかというと、コントローラーを見れば分かります。

class TasksController < ApplicationController
    before_action :authenticate_user!

    def new
      @board = Board.find(params[:board_id])
      @task = @board.tasks.build()
    end

    def create
      @board = Board.find(params[:board_id])
      @task = @board.tasks.build(task_params)

#prybugを挿入
  binding.pry

prybugを使えば、@boardや@taskなどをターミナル上で確認できます。つまりどういう情報が入っているのか視覚的に確認できるのです。

ターミナルで@taskと打ちます。
そうすると下記のような結果が返ってきました。

[2] pry(#<TasksController>)> @task
=> #<Task:0x00007fcb3fad3c18
 id: nil,
 user_id: nil,
 name: "エラー",
 description: "エラー",
 deadline: nil,
 created_at: nil,
 updated_at: nil,
 board_id: 14>

ここでも、nameやdescriptionなどがちゃんと渡ってきています。
しかし、user_idが渡ってきていません。

さて、どの情報を@taskに保管するのか決めるのでしょう?コントローラーのcreate部分を重点的に確認してみます。

class TasksController < ApplicationController
    before_action :authenticate_user!

(省略)
#フォームの内容を保存
    def create
      @board = Board.find(params[:board_id])
      @task = @board.tasks.build(task_params)

(省略)
#ストロングパラメーター
    private
    def task_params
        params.require(:task).permit(:name,:description)
    end
end

ストロングパラメーターでtask_paramsメソッドを定義しています。
これはフォームで送信された情報全て保存しないように制限するための物です。

@taskには、task_paramsの情報が保管されます。先程確認しましたが、user_idの情報がnil(=空)でしたよね?

ということは、無理やり@taskにuser_idの情報を渡せばOKじゃないですか?

@taskはハッシュの状態になっており、その中にはキーとバリューの情報が入っています。イメージはこんな感じです。

@task
= {キー:バリュー.....}

ハッシュの中にuser_id : バリューを入れてやればいけそうです。user_idのバリューは今ログインしているユーザーのidにしたいので、current_user.idとすればOKです。

current_userを使えるのは、deviseというgemのおかげです。deviseを使えば簡単にログイン等の機能を実装できます。

では、{user_id:current_user.id}を@taskのハッシュに入れるにはどうすれば良いでしょう?

エラーの解決編③ mergeメソッドを使おう

ハッシュとハッシュを結合する「merge」メソッドがRubyには用意されています。ストロングパラメーターの部分にmergeメソッドを適用してみましょう。

private
 def task_params
  params.require(:task).permit(:name,:description).merge(user_id: current_user.id)
end

イメージしにくいと思いますので、再度paramsの中身をみながら解説していきます。mergeメソッドをつける前のparamsは下記のような状態です。

{params => {
  "authenticity_token"=>"(省略)==",
  "task"=> {"name"=>"エラー",
  "description"=>"エラー"},
  "commit"=>"保存",
  "controller"=>"tasks",
  "action"=>"create",
  "board_id"=>"14"}
}

この時点では、user_idの情報が渡されていません。mergeメソッドを使うと、paramsは以下のように変化します。

{params => {
  "authenticity_token"=>"(省略)==",
  "task"=> {"name"=>"エラー",
  "description"=>"エラー"},
  "commit"=>"保存",
  "controller"=>"tasks",
  "action"=>"create",
  "board_id"=>"14",
  "user_id"=>"2"}
}

これでパラメーターとしてuser_idが渡るようになりましたね!!
ではもう一度ターミナルで@taskの内容を確認してみましょう。

[2] pry(#<TasksController>)> @task
=> #<Task:0x00007fa1d9ac5698
 id: nil,
 user_id: 2,
 name: "aaa",
 description: "aaa",
 deadline: nil,
 created_at: nil,
 updated_at: nil,
 board_id: 14>

ちゃんとuser_idが渡されていますね!!これでprybugを解除して、フォームの内容を保存できるか確認してみましょう。

これで無事解決しました!