こんにちはgengenです。
今回はRailsにおけるデザインパターンであるFormObjectについての記事になります。
FormObjectは、バリデーションや特定のユースケース向けの処理をモデルから分離する手法です。
ネット上のFormObjectについての記事ではFormObjectクラスに、操作対象になるActiveRecordのインスタンスを返すように#to_model
を実装する例をよく見ます。
このやり方は結構ピーキーで拡張性とかを考えると避けた方が無難ではないか?ということについて書いていきたいと思います。
なぜ#to_model
実装するのか
FormObjectは色々な実現方法があると思いますが、Rails: Form Objectと#to_model
を使ってバリデーションをモデルから分離する(翻訳)のようにActiveModel::Model
をincludeしたFormObjectクラスに#to_model
をオーバーライドする場合について考えます。
このような形で#to_model
を実装すると、送信先を類推するのに使われる.model_name
や、labelのやバリデーションエラーメッセージの出力に使われる.human_attribute_name
がActiveRecordのクラスに対して呼び出されるようになり、特に設定したりしなくてもscaffoldしたcontrollerやviewはほぼそのまま使うことができます。
何が問題なのか
単にバリデーションを分離するだけであればこのようにしても困ることはないと思います。
しかし、
- ActiveRecordとは属性名を変えたい
- 一つの属性を二つに分けて入力させたい
- ActiveRecordと無関係の入力を処理したい
- 他のActiveRecordモデルを同じフォームで処理したい
上記のようにActiveRecordの属性とフォームにズレが出てきた場合、そのままでの対応が難しくなります。
大元のActiveRecordのtranslation設定などをいじって無理やり対応できなくもないですが、
分離したかったはずなのに、ActiveRecordの実装や設定が特定のユースケースに引っ張られるのは本末転倒ではないでしょうか。
https://apidock.com/rails/ActiveModel/Conversion/to_model
If your model does not act like an Active Model object, then you should define
:to_model
yourself returning a proxy object that wraps your object with Active Model compliant methods.
apidock.comより
ドキュメントによるとActiveModelではないオブジェクトをActiveModelとして扱いたい時は、ActiveModel互換のプロキシオブジェクトを#to_model
に挿し込もうということなので、今回の扱いは正攻法ではないと思います。
どうしたらいいのか
困ってから#to_model
を剥がすとなると影響範囲が大きくなりますし、初めからそんなことせず愚直に送信先指定したり属性名のtranslationを設定したりした方がいいのではないでしょうか。
そもそも送信先については、
ActiveRecordからユースケース固有のバリデーションを分離する必要がある≒ActiveRecordがRESTfulルーティングと一対一で対応するような単純な設計ではない
という時点でパスヘルパーで書いた方がわかりやすくて親切だと個人的には思います。
終わりに
Rails wayからはみ出す時はきっちりはみ出した方が予後が良いんじゃないかな〜という気がしました。
ここまで読んでいただき、ありがとうございました。