railsで、1つのモデルを異なるユースケースから更新する実装例

概要

https://speakerdeck.com/yasaichi/what-is-ruby-on-rails-and-how-to-deal-with-it?slide=43 で紹介されているように、railsのモデルは複数のユースケースから更新しようとすると、モデルが複数のコンテキストを考慮した実装を強いられることとなり、相当厳しくなる旨が書かれてる。このテキストでは、複数のユースケースに(たぶん)耐えうるを実装した例を紹介する。

今回の実装では、新たに生まれたユースケースに特化した継承済みのactiverecordクラスを使った。なお、このアプリケーションでは、erbやnested_attributes_forに依存した若干複雑なフォームを作っているため、viewにactiverecordインスタンスを渡す必要があった。

class Cat < ApplicationRecord; end
class Partner::Cat < ::Cat; end

新しいユースケースであるPartner::Cat を触る画面では、確認画面で画像を持ち越す必要があるので、画像をbase64エンコードしたり、下書き保存機能があるので、ユニーク制約を回避するメソッド上書きや保存を考慮したバリデーションが書かれている。

当初は、nested_attributes_forに対応したFormオブジェクトをviewに渡していたんだけど、activerecordインスタンスをFormオブジェクトでラップしているため、method_missingを使ったり、ひどい実装になってしまったので、これ以上は改修が難しいと判断し上記の実装に書き直した。viewがactiverecordのインタフェースに依存していたのでviewへの変更はあまりなかった。

具体的な変更点

becomesを使ってviewに渡すインスタンスをキャストする必要がある

@cat = current_partner.carts.build.becomes(Partner::Cat)

(ちなみに、becomes後のインスタンスは、buildで紐づいていた関連が吹き飛んでしまう。)

paperclipを使った添付ファイルURLがインスタンスによって変わってしまうため、オプションを明示する必要がある

PartnerCatインスタンスから添付ファイルを保存すると、URLにpartner_catsが含まれてしまい、CatインスタンスからURLが参照できなくなってしまうので、 has_attached_file に以下のオプションを明示する必要がある。

path: '/cats/:attachment/:id_partition/:style/:hash.:extension",h
hash_data: 'cats/:attachment/:id/:style/:updated_at',

なお、hash_dataが必要な理由はハッシュ値を固定にするためである。

この実装をしてどうだったか

最近何でもSPAを採用がちな世の中に対して、railsでviewを作るのも悪くないと思っていたけど、複雑なフォームを作るにはrailsでは辛すぎると改めて思った。viewでactiverecordを使わない方法を模索しないといけない。

Catモデルに対して、異なるユースケースのロジックが侵食することを阻止することができたが、各所にワークアラウンドが散りばめられているので出来は80点くらい。