#1. どんなエラー?
「GroupsControllerのcreateの部分で引数が1つ返ってくると予想される(記述されている)のに、実行時には0件でしたよ」というエラーです
<エラーに関係したファイル>
class GroupsController < ApplicationController
def index
end
def new
@group = Group.new
@group.users << current_user
end
def create
@group = Group.new(group_params)
if @group.save
redirect_to root_path, notice:'グループを作成しました'
else
render :new
end
end
def edit
@group = Group.find(params[:id])
end
def update
@group = Group.find(params[:id])
if @group.update(group_params)
redirect_to root_path, notice: 'グループを更新しました'
else
render :edit
end
end
private
def group_params
params.require.permit(:name,user_ids:[])
end
end
2.原因と解決方法
まず結論から言いますと、'require'の後ろにハッシュ指定がないため、'(:group)'をつけることにより解決します。
params.require(:group).permit(:name,user_ids:[])
3.原因の見つけ方
このエラーに対する正攻法として、引数の数がおかしいというエラーに対し、じゃあ〇〇行目(今回は35行目)では何を呼び出すことが可能な状態なのかを調べるという方法があります。(実践方法は下記に記載しますので必要な方はご覧いただければと思います。)
調べた内容より、間違いの確認と、呼び出したい内容がどのハッシュに格納されているか確認ができます。これにより'require'で呼び出すハッシュを決めることができます。
(※requireを使用するときは多重ハッシュとなっている場合となるため、値の直前のハッシュを指定します。これを使わないと値を格納してほしいところにハッシュ値が入ってしまいエラーとなります。今回の例がまさにそれに当たります。)
<実践方法>
①pry-rails(デバック(確認)用のツール)をインストールするため、Gemfileの最後の行に下記のコマンドを追記する
gem 'pry-rails'
②作業中のファイルのディレクトリで bundle installする(このコマンドがわからない方は検索をかけてもらえばいっぱい出てきます)
Neverland:chat-space-kai kontatomoya$ bundle install
③指定の行を改行してその行に binding.pry を記述する(筆者の場合35行目を改行して、新しい35行目に書きます)
#1~32行目は上記の内容から変化がないので省略
33 private
34 def group_params
35 binding.pry
36 params.require.permit(:name,user_ids:[])
37 end
38 end
④rails s します
Neverland:chat-space-kai kontatomoya$ rails s
⑤エラーを吐いてしまうページにつなげます。(筆者の場合は下記の写真の登録するボタンを押すとエラーページ繋がります。)すると、次のページに飛ばずに読み込み中のままとなります。
⑥この状態のままターミナルを確認します。すると下記のようになっています。
#上にもっと記述が出ますが直接関係ないので省略します
From: /Users/kontatomoya/projects/chat-space-kai/app/controllers/groups_controller.rb @ line 35 GroupsController#group_params:
34: def group_params
=> 35: binding.pry
36: params.require.permit(:name,user_ids:[])
37: end
[1] pry(#<GroupsController>)>
⑦ここで [1] pry(#<GroupsController>)>
の隣に params と打ちましょう。すると下記のような回答が返ってきます
#続き
[1] pry(#<GroupsController>)> params #paramsを打ちました
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"/vrx3ZW1g+kcEgeh8V+FJlqUnBNEIR9jyvGeSl1r22IcMk+1F0I7zRhnKKXWpLNMtwjbmWxBBBaNR9phNk3KOg==",
"group"=>{"name"=>"", "user_ids"=>["", "2"]}, ←この部分だけ{}で囲まれており、二重(多重)ハッシュとなっています
"commit"=>"登録する", "controller"=>"groups", "action"=>"create"} permitted: false>
[2] pry(#<GroupsController>)>
まずこれを見ると呼び出すことができるハッシュがわかります。その中でハッシュ'group'のハッシュだけ2重ハッシュ(ActionController::Parametersに二回囲まれている状態)となっております。
今回筆者はその中の"name", "user_ids"がほしいと考えています。よって、こちらで'permit'したいハッシュ(:name,:user_ids[])の一つ上のハッシュ(今回は'group')を要求すれば良いとわかるいう流れとなります
(※今回の場合そのほかの値が欲しければ'require'をつけずに'permit(:先ほど調べたハッシュ名)'とすればエラーは無くなります。)
これより下は2021/1/21に追記しました。
現場で7ヶ月たった時点で分かる、今見たところでこの記述を解説
params.require(:group).permit(:name,user_ids:[])
この記述でやりたかったこと
ログインユーザーの開いているグループに紐づくユーザーの名前とuser_idをGroupに関するコントローラ、Groupに関するモデル、Groupに関するビューで使えるようにしたい。
記述に関する解説
①params
railsが準備してくれたActionControllerクラス(ファイル名も一緒)の中にあるParameters関数を呼び出しparamsオブジェクト(多次元配列のデータ)を呼び出す。(binding.pryの中身参照。ActionController::Parametersの::
は前のクラスの中で定義されている後ろの関数を呼び出すという意味)
※こいつを使う理由は"utf8"=>"✓"
,authenticity_token=>"/vrx3ZW1g+kcEgeh8V+FJlqUnBNEIR9jyvGeSl1r22IcMk+1F0I7zRhnKKXWpLNMtwjbmWxBBBaNR9phNk3KOg=="
がくっついてる状態で使ってもらえるようにするため。そうするとログイン時にユーザーを一意に判定できるため、特定のユーザーと運営(アプリ)側で他ユーザーと判別をつけながら操作を監視できる。
②.require(:group)
params配列の中にあるgroup関連の部分のデータを取りたいから呼び出してという記述。(binding.pryの中身参照)
.
は子要素を呼び出すという意味。ここでいうとParameters functionの子要素のgroup、すなわちgroupといういっぱい値を持ってる値(正確にいうとオブジェクト。この値はDBにも保存してるためテーブルとも呼べます。)を呼び出してという意味になります。
また、requireは親子関係にないクラス(ファイル)から呼び出す時に使います。(class GroupsController < ApplicationController
の部分でこの親子関係をたどっても親子関係に関する記述がないクラスに使用します。)そして今回はそれに該当しています。
③.permit(:name,user_ids:[])
さらに他のクラスで使われたくないから他クラス(ここではuserクラス)でprivate指定されてるnameという変数とuser_idsという配列も呼び出してという記述。
これのいやらしいのが、group_idで紐づくusersオブジェクト(テーブル)のnameカラムとuser_idsを呼んでという処理なのに、usersテーブルが記述に顔を出さないところなんですよね。groupオブジェクトの子の子(孫)をあたかも子のように呼べてしまっているわけです。筆者的にはデータベースに入っているデータは、クラスの中にあるオブジェクトのコピーであることを知らない初心者がこんなの分かるかって思います。
余談
久しぶりに見直したのですが、この部分は現場の仕事と同じくらいの難しいレベル感ですね。
ここのロジックがしっかり分かる方なら高い確率で自社開発狙っていけると思います。
(そして当時の筆者が全然わかってないことがわかって悲しくなりました笑)
現場でもまさに今回のように、データベースにある複数のテーブルをコントローラかモデルでinner joinして呼び出したりします。
そしてこの記事のような多次元配列として出てきたものを例えばhtmlファイルに渡して、for文で表示を行います。
なんでこんな複雑な取り出し方をするのか昔は疑問でしょうがなかったのですが、今なら分かるため皆さんに共有します。
打ち込む文字数が減って、書くのが楽
でかつ読みやすくなる
からです。
(密度の薄い表現が減ります。日本語でいうところの漢字を使用している理由と同じです。ぜんぶひらがなでかかれるのいやですよねわら。←気持ちとしてはこれを読んだり書いたりしたくないのと同じです。)
①開発の現場なら
現場はタイムイズマネーになるので開発速度が非常に重要となります。(経営目線ならライバル企業に先行するため。雇用者目線ならエンジニアは基本時間労働となるため。)そのため、開発の場面ならいかに同じ内容の記述をしないかが開発速度を考える上で大事になります。ゆえに他のファイルで書いてる同じものを使いたいなら、コピペはもちろん書き写すなんてもってのほかとなります。(そういうものは大抵複数箇所で使われるため作業時間に差が開くからです。また記述がすっきりするためミスしにくくなるというところも大きなメリットとなります。)
こういう場合はrequireで呼び出すかpublic変数、public関数などで準備してファイルを跨いで使い、記述量(作業量)を最小にしていくのですが、今回のものはそのいい事例のものとなっています。(でも初心者に対しては難易度高すぎですよね。。。)
②運用保守の現場なら
運用保守の場面なら、いかに見なくていい部分を見ずに、急所の部分だけ読めるかが大事となります。そのため、まとめられた部分が多い方が「今回のエラーと関係ないから読まない」とばっさり切り捨てやすくなるのでとてもありがたいのです。最初こそrequireてなに?このドット何?と調べっぱなしですが慣れるととても読みやすくなります。
※筆者の会社は両方の業務があり、どちらも体験させてもらっているため両方の目線からかいてみました。