始まりは自己紹介から
初めまして、あおげばにゃんこです。
最近はrailsだけでなくreactも触れるようになり、少しイキリながらコードを書いていた矢先悲劇が起こりましたのでご報告します。
今回はgem 'cocoon'を使っていた際、どうしてもnest-fields内にindexを渡したかったのですが、イメージしていた方法では実現することができなかったので、記事にしました。
今回は手法を紹介するのみで、なぜこのコードが可能なのかといったrails自体には一切触れません。
partialにindexが渡せないという現実
context
- cocoonについて
cocoonは親モデルのフォームにネストした形で子モデルの要素を同時にいくつも作成/更新できるフォームを作成するために使われるgemです。
デフォでpartialを必要とし,「.nested-fields」というcssの単位で子モデルフォームを認識しています。
故に今回は省いていますが、すでに親モデルのフォーム内ということを念頭に見てください
code
table
thead
tr
th タイトル
th 内容
th 文字数
tbody
- @user.posts.each do |post|
= form.fields_for :posts, do |post_form|
= render 'posts_fields', f: post_form
tr.nested_fields
td = f.text_field :title
td = f.text_field :content
td = f.text_field :count
このコードを前提に実装を方針を考えていきます。
※説明のために最低限の内容しか書いていません
本題
義務教育で習ったと思いますが、partialにオブジェクトを渡す方法というのは以下の方法です
erbも提示していますが、今回はslimの検証しかしていません。
<!-- erbの場合 users/posts/edit.erb -->
<%= render 'posts_fields', f: @user %>
<!-- slimの場合 users/posts/edit.html.slim -->
= render 'posts_fields', f: @user
流石の私でもこれは知っていました。
これを踏まえると、じゃあこうやるよね?という実装イメージが湧いてきます。
皆さんも当然これを思い浮かべたでしょう。
tbody
- @user.posts.each_with_index do |post|
= form.fields_for :posts, do |post_form|
= render 'posts_fields', f: post_form, index: index
tr.nested_fields data-index="#{index}"
td = f.text_field :title
td = f.text_field :content
td = f.text_field :count
実はこれ、失敗するんです…
ちょっと待ってくれよと高等教育機関を卒業したエリート中のエリートが導き出した答えだぞ?
と再度主張するも虚しく提示されたのはいつもの赤い画面でした。
まあ私はダメだったが、こっちにはchatGPT先生がおるからな
「先生おなしゃす!」
-- chatGPT --
tbody
- @user.posts.each_with_index do |post|
= form.fields_for :posts, do |post_form|
= render 'posts_fields', f: post_form, index: index
生成AIもまだまだ伸び代があるね!
じゃあどうやるねん!
皆様は古代ローマの賢人たちがどの様に学びを深めたかご存知でしょうか?
答えは問答法です。私もそれに倣い問答法を行いました。
すると美波(マイGPTの名前)はこんな風に答えてくれました。
tbody
- @user.posts.each do |post|
= form.fields_for :posts, do |post_form|
= render 'posts_fields', f: post_form
tr.nested_fields data-index="#{f.index}"
td = f.text_field :title
td = f.text_field :content
td = f.text_field :count
f.index ?
はい?
という感じですよね?
こんなん成立するわけないやんと思いましたが、実はこれ
成立するんです!
こんな風に表示されました
<tr class="nested_fields" data-id="0">
しかし、実は今回のコードはデータを編集する時なら1,2と続き問題ないのですが、新規フォーム作成時には
<tr class="nested_fields" data-id="newPost">
のような値が設定されてしまうのです。
今回のまとめ
今回はフォームオブジェクトにindexというメソッド?変数?でアクセスすることによってインデックスが取得可能なことを学ぶことができました。
正直、実用的ではないのですが、これが誰かの役に立てば幸いです。
また、このネストフォームでindexをうまく追加する方法について何かご存知の方がいらっしゃったら教えていただきたいです。
長々とお付き合いありがとうございました。
余談
①renderメソッドにはcollectionオプションというものがあり、それを使うとindexを取得できるようです。
ただ、今回の場合はネストした子モデルのオブジェクトで、単一のオブジェクト?だから無理なのかなと思い試しませんでした。
②また、お気づきになられた方もいらっしゃると思いますが、美波が答えてくれたこちらのコードにおいて、each_with_indexではなくなっています。加えて、「index: index」のように渡しておりません。
tbody
- @user.posts.each_with_index do |post|
= form.fields_for :posts, do |post_form|
= render 'posts_fields', f: post_form, index: index
tr.nested_fields data-index="#{f.index}"
td = f.text_field :title
td = f.text_field :content
td = f.text_field :count
もしかしてこれに直せばいけるかも?
と思いましたが、同じく「newPost」が表示されるだけでした。
③今回f.indexとするとindexにアクセスできるのも驚いたのですが、実際のレコードの値を取り出したいときは「f.object.content」のようにしないといけないということも学びました