【Rails】フォームの内容が保存されない問題 mergeで解決

今回はフォームに必要事項を記入したのに、投稿できない問題の解決策について解説していきます。

まずは前提条件を整理しておきます。

前提条件

①Board,User,Taskのモデルを使用
②それぞれのモデルの関係性は下記の通り

  • Board has many Tasks & Tasks belongs to Board
    (ボード1つに対して、複数のタスクが紐づく)
  • User has many Tasks & Tasks belongs to User
    (ユーザー1人に対して、複数のタスクが紐づく)
  • User has many Boards & Boards belongs to User
    (ユーザー1人に対して、複数のタスクが紐づく)

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

不具合について触れる前に、実際にどのように実装したかったのか見てみましょう。ユーザーが必要なタスクを追加できるアプリみたいな物を想像してください。

①「Add new card」ボタンをクリック

②新規カード作成フォームが出現

③新規カードが一覧画面に追加

こんな感じでちゃんとカードを保存して、一覧画面に表示できればOKです。

不具合を確認してみよう

次に今回起きた不具合に関して、説明していきます。

①「Add new card」ボタンをクリック

②新規カード作成フォームが出現

画像では、フォームが空欄ですが実際は記入してから保存ボタンをクリックします。

③新規カードが保存されない&User must existが出現

保存を押したのに、「User must exist」というエラーが出て保存できませんでした。

問題を特定しよう

ここから何が原因なのか検証していきます。

可能性① ActiveRecordの問題?→結論:問題なし

まず僕が考えたのは、Activerecord上UserモデルとTaskモデルが紐づいていないのかもしれないと考えました。なので、modelsの中にあるuser.rbとtask.rbを確認していきます。

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

事前に僕がhas manyでuserとtaskモデルを紐付けていたので、OKそうです。
次にtask.rbを確認しましょう。

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

ここでもbelongs_toでUserモデルと紐づいているのが確認できました。なのでこの段階で、Activerecord上は問題が無いことが分かりました。

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

次にデータベース上、UserモデルとTaskモデルが紐づいていないかもしれないと考えました。例えば、Taskモデルにuser_idみたいなキー(外部キー)が無いなどの可能性が考えられます。

現状のモデルの状態を確認するには、dbフォルダにあるschema.rbを確認します。スキーマを見れば、全てのテーブルの状態を確認できるので、こまめにチェックすると良いですね。

色々と書いていますが、チェックポイントはtasksテーブルが、userの情報を取れる状態になっているか見るだけでOKです。

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

カラムに「user_id」が存在しているので、TaskとUserは紐づいていることが分かります。なので、データベースに関しても問題なさそうです。

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

最後は、コントローラーの問題かなと考えました。なので、コントローラーのnewとcreateの部分を重点的に確認していきます。

まずは、createの部分でデバッグツールprybugを使えるように「binding.pry」を入れて処理を止めます。

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).merge(user_id: current_user.id)
    end
end

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

ステップ① paramsの内容を確認しよう

まずはターミナルで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メソッドは{}(ハッシュ)の形で返すということです。ハッシュの中には、キーバリューが存在しています。

この辺の用語はRailsというより、Rubyの知識なので分からない方は復習してみてください。どれがキー・バリューなのか分かれば今の段階では、全く問題ありません。

では、ハッシュの中身をシンプルに分解すると、こんな感じです。

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

taskというキーのバリューは、またまたハッシュになっています。その中にキーとバリューの組み合わせが存在しています。

その中にフォームで入力した「エラー」という文字がちゃんと入っています。なので、フォームで入力した情報はしっかり渡っていることが分かりました。

ステップ② @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メソッドです。

ストロングパラメーターの部分にmergeメソッドを適用してみましょう。

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

もう一度ターミナルで@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を解除して、フォームの内容を保存できるか確認してみましょう。

無事、解決しました!

まとめ

フォームの内容が保存できないのは、今回紹介した原因に限らないと思いますが、考え方やチェックする項目については参考になるかと思います。

特にフォーム内容の保存を担うのは、どこなのか?を意識すると今回のようにコントローラーのcreate部分に問題があると気づけます。

あとはprybugなどのデバッグツールを使って、どんなパラメーターが渡ってきているのか確認すると解決しやすいですね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です