Railsのファットモデル 3つの対処法

『Railsのファットモデル 3つの対処法』のサムネイル

はじめまして、Bilbase株式会社代表の佐藤です。
皆さんはファットモデルについて聞いたことはあるでしょうか?

最近個人的に質問を頂くことがあるのですが、その中でいくつか共通している質問があります。それは、「ファットモデルの回避方法」についてです。
今回は、そんなファットモデルの回避方法の一部についてお話ししようと思います。

本記事では以下の内容をお話していきます。

まずは結論

・ファットモデル自体は問題でなく、モデルに書くべきではない処理が多いことが問題。
・モデルに書くべき処理が多く、コード量が多いのは問題ではない。

最終的には上記の結論に至ったわけですが、「割と当たり前の結論になったな〜」と言うのが私の感想です(笑)。

しかし、今回この内容で書く機会が得られてよかったとも思っています。
なぜなら当たり前の中にこそ、落とし穴は潜んでいるものだからです。

この言葉に思い当たることがあるエンジニアの方は一定数いらっしゃると思います。

だからこそ、自分をほんの少しだけ疑いつつ、当記事を読み進めていただけるとうれしいです。


では早速中身を見ていきましょう!

ファットモデルに陥るサイクル

ファットモデルに陥るパターンを理解していただくと、ファットモデルの根本的な解決方法がわかりやすくなると思いますので、はじめに書いていきたいと思います!

まずは下記の図「Railsがファットモデルに辿り着くまで」をご覧ください。

図をご覧いただくと「確かに」と思われる方も多いと思います。
RailsではSkinny Controller、Fat Modelという方針が推奨されていますので、MVCモデルで考えていただくと、おそらく以下の順序でファットモデルができあがって行くのだと思います。

Railsがファットモデルに辿り着くまで

解説

  1. まず、ほかのフレームワークもさわったことがない初心者の方が陥るのがファットビュー。ここは説明不要でしょう。APIモードで動かしている場合は関係ないですね。
  2. Railsに慣れ始めたエンジニアが陥るファットコントローラー。モデルに書くべき処理とコントローラで必要な処理の使い分けがうまくできずに、Controllerにロジックが集中するケース。このケースは割とススっと抜け出せると思います。
  3. 問題はここ。しっかりModelにロジックを寄せていった結果待ち構えているのがファットモデルです。ViewとControllerをスッキリできた分Modelにロジックが集まるのは当然ですよね。


どこに何が書いてあるのかわからないとてもつらいファットモデルの状況になってしまいます。

今すぐ使える【3つのファットモデル解消方法】

ここでは紹介できる数も限られてきますので、導入・実装までの手順を3つだけ紹介していければと思います。

◆ActiveDecorator

https://github.com/amatsuda/active_decorator

❏ActiveDecoratorとは
Modelに対応するViewのためにDecoratorを利用しやすくするgem。

Viewに表示する情報量が多くなるとコールバックが増えがちになってしまいます。
また、インスタンス変数が増えすぎてしまう等、最終的に何が実行されているのかわかりづらくなってきたりした場合は、別の手段を考えるいい機会かもしれません。

ここではひとつの対策として、Decoratorを使って対応する方法をお伝えしていきます。
Decoratorの特徴としては、Viewでの見せ方なども任せることができる点です。

View、Controllerをスリムにするために、Modelにより過ぎてしまったコードをDecoratorに移してあげることで、それぞれのファイルの役割を明確にすることもできます。

また、保守性の観点からもDecoratorクラスを変更するだけで共通化して変更が可能になる点でも便利だと思います。


※注意点
変更時の影響範囲を考えながらDecoratorを作っていきましょう。
保守性を高められるからといって、あまりViewに依存した作りになってしまうと、逆に汎用性が下がり、メンテナンス工数が増大してしまうこともあります。
APIを作る際にもdecorateしてからレスポンスしたいかもしれませんし、ほかのページでも使い回すことがあるかもしれません。
ユースケースが限定されないように設計していくのも、エンジニアのお仕事のひとつですので注意しましょう。

HTMLなども含めてdecorateするのは、UI層(ViewやHelper)の責任範囲になるべきものだと思います。UI層の使い分けも意識した方が、よりメンテナンス性の高いアーキテクチャになると思います。


❏Userモデルでの利用シーン
full_name(姓名)を作りたいときに、first_name(姓)last_name(名)を組み合わせてレスポンスしたい場面があると思います。

class User < ApplicationRecord
  def full_name
    first_name + last_name
  end
end

# Viewでの使用例
@user.full_name

プロジェクトが大きくなるにつれて、このようなメソッドがModelに多数発生し、Modelが膨大するかと思います。

そのような状況を解消する手段としてActiveDecoratorというGemを利用できます。


❏使い方
ステップ1.
Gemfileに以下を追記し、bundleを実行します。

Gemfile
gem 'active_decorator'

ステップ2.
ターミナルで以下のコマンドを実行して、既存モデルに対してDecoratorを作成します。
app/decorators/user_decorator.rb ファイルが生成されることを確認してください。
※ここではuserモデルの場合を想定して実行します。

ターミナル

$ bundle exec rails g decorator user

ステップ3.
app/decorators/user_decorator.rbに今までModelに集まっていたView向けのロジックを移動してあげましょう。

app/decorators/user_decorator.rb
module UserDecorator def full_name first_name + last_name end end # Viewでの使用例 @user.full_name

このようにDecoratorに任せることで、ファットモデルを回避しつつ、メンテナンス性を高めることができると思います。

◆ActiveModelSerializers

https://github.com/rails-api/active_model_serializers

❏ActiveModelSerializersとは
Railsで素早く簡単にjsonを作れるgem。

APIモードで利用される際は、json形式でレスポンスすることが多いと思います。ただし、その制御に細かな設定をすると、ControllerやModelが汚れて肥大化してしまいます。
そんなときに使えるのがActiveModelSerializersです。

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


❏使い方
ステップ1.
Gemfileに以下を追記し、bundleを実行します。

Gemfile
gem 'active_model_serializers'

ステップ2.
ターミナルで以下のコマンドを実行して、既存モデルに対してSerializersを作成します。
app/serializers/user_serializers.rb ファイルが生成されることを確認してください。
※ここではuserモデルの場合を想定して実行します。

ターミナル

$ rails g serializer user

ステップ3.
app/serializers/user_serializers.rbに今までModelやControllerに集まっていたロジックを移動してあげましょう。

app/serializers/user_serializers.rb
class UserSerializer < ActiveModel::Serializer attributes :id, :first_name, :last_name, :first_name_size def first_name_size object.first_name.size end end # Controllerでの使用例 @user = User.first render json: @user

レスポンスされるjson

{
  id: 1,
  first_name: 'sato',
  last_name: 'motonori',
  first_name_size: 4
}

このようにSerializerを使うことによってControllerやModelが綺麗になると思います。

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

ActiveModelと切り離して考えられる処理は別オブジェクトに移動させられます。

❏例
外部APIへの問い合わせはapp/clients配下にクラスを作って行う。
Redis関連の処理をapp/cachesに別オブジェクトとして切り出す。

このようにActiveModelと切り離して考えられる処理は、切り離して個別でメンテナンスできるようにすることができます。

まとめ

記事を読んでいただきありがとうございました!
今回は「ファットモデルの回避方法」ということで記事を書かせていただきましたが、開発チームの方針、開発の優先度によって、課題に対してのアプローチ方法はもちろん異なってきます。
どちらかというと、最初に結論でも述べたとおり「ファットモデル自体は問題でなく、モデルに書くべきではない処理が多いことが問題」なわけです。

もっと抽象化すると「これってここにあるべきなのか?」という問いに対して思考できているかということになりそうですね。

エンジニアのみなさまへ

フリーランスとしてより良い職場環境に行きたい・会社員だけどフリーランスになりたい等のお悩みはありませんか?
エンジニアファーストを運営している株式会社グラントホープでは転職の相談を受け付けております。

  1. 1.スキルに見合った正当な報酬を
  2. 2.忙しく働く方へ自分と向き合う時間を
  3. 3.キャリア形成のサポートを

みなさまへ新しい働き方を提案し、オンラインや対面のご相談でご希望に沿ったキャリア形成を全力でサポートいたします!

登録・応募はページ
チャットボットから!

佐藤元典(Bilbase株式会社)

佐藤元典(Bilbase株式会社)

Web開発を通じて建設企業の課題解決を行うBilbase株式会社 代表。バックエンドエンジニアを経て現在はプロジェクト統括も行っています。グラントホープさんを通してエンジニアメンバーも募集予定!