Rails: 親子まとめてのバリデーションは削除時に注意


Railsでは、親テーブルの1行に子テーブルの複数行が対応するhas_manyの関係を簡単に作れます。入力などの補助的な機能も各種整っていますが、バリデーションをかけようとした時に失敗したところがありました。

TL; DR

  • accept_nested_attributes_forで削除しようとした子要素も、バリデーション時点ではまだ削除されていない
  • そのため、モデルのCollectionにも入ったまま
  • marked_for_destruction?でチェック可能

親子セットでのデータハンドリング

「1本の記事に複数のコメントが付く」ような、一対多の関係はWebアプリを作っていれば至るところに発生します。Railsでは、has_manyで結ばれた関係について、accept_nested_attributes_forを行うことで、親についてのフォーム送信で子の作成・変更・削除などを一気に行ってしまうことができるようになります。さらに、nested_formを使えば、そのような親子関係を受け入れるような入力フォームも、簡単に構築できます。

親子モデルへのバリデーション

モデルへのバリデーションは重要な機能ですが、親子セットのモデルでも親にかけたバリデーション、子にかけたバリデーションはそれぞれ動作します。そこで、子モデルの合計値を使うようなバリデーションをかけてみたところ、一見正常に動くように見えました。

消えてなーい!

ところが、その状態で子モデルを削除しようとしたところ、うまくバリデーションを通らないという事態が発生してしまいました。調べてみると、バリデーション段階では子モデルが消えていないということが判明しました。そして、destroyed?falseで、事態を解決できません。

正しいチェック方法

調べまわった結果、marked_for_destruction?というのがこの状況をチェックする適切なメソッドだと判明しました。collections.reject(&:marked_for_destruction?)で、削除されずに残るモデルたちだけのリストが得られます。

参考リンク