Railsのファットモデル問題に対処する前に読んでほしい記事

背景

Skinny Controller, Fat Model

Railsではスキニーコントローラー、ファットモデル(Skinny Controller, Fat Model)という方針のもと、
コントローラーのコード量を少なくして、モデルを分厚くするという書き方が推奨されていました。

10 Ruby on Rails Best Practices — SitePoint
Rails Best Practices 1: Fat Model – Skinny Controller

このような背景から、ファットモデルという設計が目指すべき設計という認識となりました。

「ファットモデル問題」の登場

ところが、原因はわかりませんが、次第にファットモデルが問題があるものとしてみられるようになりました。
界隈では「ファットモデル問題」として取り上げて解決するという方法が紹介されるようになります。

2012年の英語の記事が全ての始まりらしいです。
7 Patterns to Refactor Fat ActiveRecord Models

それを日本語で解説した記事が投稿されて広まったのかもしれません
肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

これらの記事で言われていることは

  • ActiveModelを分割して、責任を小さいオブジェクトに分ける
    • サービスクラス、フォームクラス等

というものです

この考え方に影響を受けて、2013~2015の時期は色々な方法が編み出されたようです。

多くの記事では

  • オブジェクト指向
  • ドメイン駆動設計

を用いてActiveModelを分割の方法を提案していました。

問題

提案された方法ではフォームクラス、バリデータークラス、サービスクラスなどが提案されました。
なかでもサービスクラスと言うものが有名になり、多くのプロジェクトにたくさん導入されました。
憶測なのですが、サービスという言葉が日本人に誤解を生みやすい(サービス残業、レディースサービス等)ので、使われたのかもしれません。
FormやValidatorはよくわからないけど、Serviceだったら理解できるという心境だったのでしょう。

※ 私も当時、モデルにロジックを書かないような設計を教えられました。なので当時から保守を行っているようなアプリにはサービスクラスが導入されています。

結果から言うと、多くの人が提案した「ActiveModelを分割」はほとんど失敗だったと思います。

オブジェクト指向、ドメイン駆動設計などを取り上げて提案していましたが、殆どの人には理解されず

  • 悪いコードを分離させて切り出すためのクラス
  • 単にコード量を減らすため

といった方法で使われたのがほとんどだと思います。
私のところでは、悪いコードの避難所としてサービスクラスなどのオブジェクトが使われたため、有効に使えたプロジェクトは少なかったのでは無いでしょうか。

そのような状況だったので2016年には「ActiveModelを分割」は失敗だったという記事が多く書かれました。

なぜ失敗したかは以下のような理由だと思います

  • 責任の分離の設計や考え方に個人差があり、統一できずに無秩序になってしまった
    • 同じRailsなのに書き方がぜんぜん違うという現象
  • 「ActiveModelを分割」という考え方自体がRailsの思想と離れているので、バージョンアップが大変
  • 方法は素晴らしいが、実際にやってみると簡単ではなかった

ファットモデル問題とは

ここで改めてファットモデル問題についてまとめます。

  • ファットモデルは目指すべきものなので、ファットモデル自体が問題ではない
    • モデルに書くべきでない処理を書くということが問題
    • モデルに書くべき処理によってコード量が多いのであれば、それは問題ではない
  • 仕方なく肥大化してしまったときに「ActiveModelを分割」という方法が提案された
    • 下手に分割をしてしまって負債を生む結果となってしまった

記事を参考にする時

Qiitaなどの記事にある、独自の考え方や、設計を参考にするときは以下を注意して読んでみると良いと思います。
※ 失敗であったという意見があるにもかかわらず、「ActiveModelを分割」する考え方の記事は、国内外で今でも参考にされています。

  • それを実践するとどうなるのか予測を立てる
  • 古い記事であれば、その後どうなったのかを調べる(←重要)

以上を踏まえて解決する

ファットモデルを解決するという方法はいくつかあります。

モデルに書いてはいけない処理がたくさんある

殆どの場合はこの方法だけで解決します。

ActiveDecrator

amatsuda/active_decorator: ORM agnostic truly Object-Oriented view helper for Rails 3, 4 and 5

Viewで使うような処理をモデルから切り離すことができます。

CarrierWave

carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks

紐付いた画像を保存するためのメソッドや、サイズを変換するメソッドがモデルに書かれている場合は、
CarrierWaveをつかってUploaderへロジックを移しましょう。

ActiveModelSerializers

rails-api/active_model_serializers: ActiveModel::Serializer implementation and Rails hooks

APIだけで使うメソッドがあるのであれば、Serializerへ移動させましょう。

別のオブジェクトとして切り出す

ActiveModelに依存しない処理は、別のオブジェクトとして切り出しましょう。

  • Redisに保存する処理をapp/cachesという別オブジェクトに切り出して、ActiveRecordのように操作できるようにする
  • APIへの問い合わせ処理はapp/clientsに作って、そのクラスの中で通信処理を行う

app/xxxxxの命名規則は未だに議論を呼ぶのでちゃんと考えたほうがいいです。

ActiveModel自体を分割したい

Trailblazer

自力で分割するよりもTrailblazerに則って設計をしたほうが楽です。

trailblazer/trailblazer: A High-Level Architecture for Ruby.

「ActiveModelを分割」のようなことを行えるGemです。
Railsでビジネスロジックをきちんと書きたいときに便利です。
方針としてはモデルにロジックを書かずに、オペレーションというオブジェクトにロジックを書いていきます。

参考
* Trailblazerを使い、Railsのモデルの肥大化問題からサヨナラする - Qiita

まとめ

  • Railsのレールに沿って開発することが一番のベストプラクティス、最初はファットモデルでも問題ない
  • 「ActiveModelを分割」は負債となりやすいので、行わない
  • モデルに書くべき処理とモデルに書いてはいけない処理を考えてみる
  • どうしても解決したい場合はTrailblazerについて調べてみて、導入を考える
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.