accepts_nested_attributes_forは本当に非推奨なのか[Rails]
概要
rails accepts_nested_attributes_for
でググると非推奨
がサジェストされたり、これを使わずに別の方法を取っているような記事が複数ヒットします。
私自身も結構前にコードレビューで「非推奨だよ〜」みたいなコメントをもらったことがあり、その時はそのまま直さなかった気がするのですが、また実装する機会があったので今どういう状況なのか改めて調べてみました。
チーム開発での話なので、個人で使う使わないは自由にしてください。
いつ誰が非推奨と言ったか
非推奨なら普通deprecated
の警告が出ますよね?
非推奨のソースってどこなんでしょう。
↓ここのようです。
dhh on 15 Nov 2016
I'd actually like to kill accepts_nested_attributes_for in due time.
Don't think we should promote it for this new API.
Rather, let's just show how to do it by hand in the controller.
発言者は、dhh。
発言場所は、railsへのPR(helperへのメソッド追加)に対するレビューコメント。
発言した時期は5年以上前。
コメントを日本語にするなら「そのうち消したいと思ってるんだよね」くらいのニュアンスでしょうか。
5年以上前のふわっとしたコメントで、明確な理由や代替案・計画があるというわけでもない。
う〜ん、使うのはやめたほうがいいのか実に微妙です…。
関連する記事
https://discourse.clean-rails.org/t/model-form-1-1/14
4年以上前の記事。
ここでは、よく名前を見るjokerさんやwillnetさんも発言しており、使いにくい部分があることは確かな様ですが、結論を出すまでには至ってません。
この記事の時点では、jokerさんは強く否定派、willnetさんは「わかって使う分にはいいんじゃないのか派」なようです。
https://techracho.bpsinc.jp/hachi8833/2019_11_05/82601
2019年の記事。
accepts_nested_attributes_for
に対するコメントがまとめられていますがこのメソッドが憎くてたまらないって雰囲気がでてます。
それだけつまづく人が多いってことなんでしょうね。
Rails7でaccepts_nested_attributes_forのアップデート
delegated_typeへの対応というそれ自体はあまり使わなそうな内容ですが、消すつもりのものをアップデートしますかね?
結論
使いたくないと考えている人はかなり多いようですが、「非推奨だから使わないほうが良い」と断言するのは言いすぎな気がします。
落とし所としては、チーム内の方針として使わないと決めるのはあり、扱えると判断できるくらいのケースなら使えばいい、といったところでしょうか。
実装してみてコードが複雑そうならやめるとか、チーム内のルールで「使っても良いが○○のようなケースでは複雑になるので使わない」とかバランス良く考えられると一番良さそうです。
つまづきの先(余談)
これはポエムですが、Railsってこういう「シンプルに使えば使いやすいけど、ちょっと複雑なことをやろうとすると面倒くさくなる」ものって結構あると思うんですが、その面倒くさい部分まで含めて時間をかけてちゃんと理解をすると、次回の実装からめちゃくちゃ便利な道具になったりします。
なので、時間がないときにそういうことをする必要はないですが、時間に余裕があるのならあえてつまづきに行ってみるのも面白いと思います。
代替案
代替案についても少し言及してみます。
本当にFormオブジェクトはおすすめできるのか?
代替案としてFormオブジェクトを挙げている記事がたくさんあってびっくりしましたが、残念ながらこれは、使えば問題を簡単に解決してくれる銀の弾丸ではありません。自前で実装する、ということを意味します。
ちなみに、私が実装するならFormオブジェクトではなくてPOROクラス(何も継承しないクラス)として作るのが好みです。
ActiveModel
をincludeするとできることが増えて責務が大きくなりがちなので、パラメータ周りの最低限のことだけPOROクラスに任せてバリデーションはデータを持つテーブルのmodelに任せます。
まあ、この辺は代替案というよりはビジネスロジックをどこに置くかの話になってきますね。
合わせて知っておきたいメソッド
autosave: true
親のsaveの際に子のバリデーション、save、transactionをやってくれるhas_one
,has_many
のオプションです。
accepts_nested_attributes_for
がフロントとの兼ね合いでパラメータ構造が合わないので使えない、という場合なんかにもこれを使う選択肢はありそうです。
ちなみに、accepts_nested_attributes_for
は内部的にautosave: true
しています。
詳しい使い方はこの辺の記事を見てください。
参考: https://qiita.com/shyamahira/items/d6592bcfda77c53e19b1
mark_for_destruction
子レコードの削除が必要なケースにmark_for_destruction
を使います。(_destroy
パラメータを使うことでも削除はできます。)
これは私はaccepts_nested_attributes_for
の指定が必要だと思っていたのですが、どうやらautosave: true
することで使えるようです。
便利ですが、destroyではない方法でレコードを削除するという若干特殊な削除方法にはなりますね。
参考: https://qiita.com/mogulla3/items/010d5b057c00910c085e
accepts_nested_attributes_for実装時の進め方(問題の切り分け方)
accepts_nested_attributes_for
でハマる原因として、かなり影響範囲が広いメソッドであるために問題点の切り分けができていない事が多いのではないかと思います。
進め方としては、
- viewなしで正しいパラメータ(attributes)が送られれば、正しい値がmodel・子modelにセットされる事を確認する
- 正しいattributesがmodelにセットされた後saveした時に正しく更新できることを確認する。
また、ここまでをrequest specに書いてサーバー側の処理に問題ないことを保証する -
fields_for
などのviewの記述を追加した後、ページのhtmlソースを見てパラメータ構造・パラメータ名が正しいことを確認する - railsのdevelopmentログなどで正しいパラメータがsubmitされることを確認する
- controllerでmodelに渡しているパラメータが正しいことを確認する
といった具合に進めればどこに問題があるかの切り分けができて進めやすくなるでしょう。
また、web上のコピペできそうなサンプルを動かしてみて、何がそのコードと異なっているのかを比較するというのも良いでしょう。
補足
コメント欄に補足あり
宣伝
こういったRailsの技法・tipsをまとめた本をkindle unlimitedで公開中です。
良かったら読んでみてください。
Discussion
補足
Webの情報では
accepts_nested_attributes_for
は使うべきでない、今どきはもう使ってない!みたいな感じの情報のバイアスがかかっているように思います。
(プログラミングスクールがそういう風に教えている?)
これらは不正確であるためRails初心者がこういった認識を持ってしまうのは悲しいです。
より正確には以下のように順を追って理解する必要があります。
accepts_nested_attributes_for
を使っているシステムもあるし正しく使えれば便利なので、使う方法・使わない方法どちらもできたほうが良いaccepts_nested_attributes_for
を使わないとしてもformオブジェクト作成は必須ではないaccepts_nested_attributes_for
またはautosave: true
を使わない場合、以下のような処理をする必要があるtransaction
内で関連modelを更新するようにする(どちらか片方のmodelだけがエラーになってもrollbackできるようにする)※私はformオブジェクトを使うよりもこういう感じのもっとシンプルなサービスクラスの実装方法をおすすめしたい(コツ・おまけの部分は除く)
accepts_nested_attributes_for
を使う場合・使わない場合の複数model更新処理のイメージaccepts_nested_attributes_for
を使わない場合accepts_nested_attributes_for
を使う場合