日々Derailment

技術的なことの備忘録。脱線事故が起きまくってるので、なるべくrails wayを守れるよう頑張ります。

Otemachi.rb#20でLTしてきた

概要

Otemachi.rb#20で「突撃!隣のフォームオブジェクト」という話でLTしてきました。
タイトルは某番組をインスパイアしたものの、内容はこちらのフォームオブジェクトに関する取り組みについてなので、完全にタイトル詐欺である。

スライド

speakerdeck.com

内容について

責務がごっちゃでどこに何書いてるかわけわからんカオスなプロダクトを、制度変更に際して色々と整理している。
その中でフォームオブジェクトを採用しており、それをActiveTypeというgemで対応した。

ActiveTypeについて

めちゃくちゃざっくり言うと非ARモデルをARオブジェクトっぽく扱えるようにして、かつARオブジェクトもいい感じに継承できるっていうgem。

github.com

class HogeForm < ActiveType::Object
  attribute :name, :string
  attribute :email, :string

  validates :name, :email, presence: true
end

みたいなのがかけたり

class User < ApplicationRecord
  has_one :address, dependent: :destroy
end

みたいなARオブジェクトを

class FugaForm < ActiveType::Record[User]
  change_association :address, class_name: 'AddressForm'
  accepts_nested_attributes_for :address
end

みたいに完全に置き換えれたりする。

スライドに入れれなかったけどcreateとかupdateとかのコンテキストに応じて処理が違うのであれば、共通の処理はxxxForm::Baseに書いて、それを継承してxxxForm::Createとか、xxxForm::Updateみたいなこともできたりする。
(くっそ雑なので継承に関しては用法用量を正しく守ってお使いください)

class UserForm::Base < ActiveType::Record[User]
  # 関連付けとか共通な処理
end

class UserForm::Create < UserForm::Base
  # create用の処理
end

class UserForm::Update < UserForm::Base
  # update用の処理
end

ちなみにupdate等の処理に際してActiveTypeオブジェクトに変換する際には

user = User.find(params[:id])
@user_form = ActiveType.cast(user, UserForm)

といった感じでActiveType.castメソッドを用いると変換できる。

懇親会

懇親会で話があったのが、ARオブジェクトのバリデーションをFormオブジェクトに完全に移譲するとバッチ処理とか大丈夫なのか、と言う話があった。
これはたしかにけっこう悩みどころで、直接ARオブジェクトを操作することをいかに禁止するかというのはなかなか難しい部分だなと思う。
仮にバリデーション忘れてもpresence: true程度であれば、db上でnull: falseをきっちりかけてればこと足りるけど、複雑なバリデーションは対応難しいっていうのは間違いない。
ただ、バッチ処理に関してはActiveType.castの一手間がかかるけど、その1行の手間で責務のカオスさが減るならいいのかな、とは思ってます。 (エンジニアが一人だからOKな話で、ほかに強い人いたら即NGくらうかも)

この辺の各オブジェクトの責務と言う部分に関しては相談する相手等がいなくて真剣に分からないので、だれか教えていただきたいところです。

LTを終えて

だいぶ詰め込みすぎて、早口で喋らざるを得なくなってしまった。
喉のコンディションが悪かったのもあって、聴きづらかったであろうことは申し訳なかったなと。
あと、castの部分とかも話せてなかったので、だいぶ分かりにくくなってしまった気がする。
5分だからこそ伝える内容うまく絞らないとなー。

でも、やっぱり前で話すの楽しいよね。
やるまでは「なんでLT枠でエントリーしちゃったかなー」とか思うんだけども。
不思議なものである。