フリーランス
エンジニア
Railsのファットモデル 3つの対処法
はじめまして、Bilbase株式会社代表の佐藤です。
皆さんはファットモデルについて聞いたことはあるでしょうか?
最近個人的に質問を頂くことがあるのですが、その中でいくつか共通している質問があります。それは、「ファットモデルの回避方法」についてです。
今回は、そんなファットモデルの回避方法の一部についてお話ししようと思います。
本記事では以下の内容をお話していきます。
まずは結論
・ファットモデル自体は問題でなく、モデルに書くべきではない処理が多いことが問題。
・モデルに書くべき処理が多く、コード量が多いのは問題ではない。
最終的には上記の結論に至ったわけですが、「割と当たり前の結論になったな〜」と言うのが私の感想です(笑)。
しかし、今回この内容で書く機会が得られてよかったとも思っています。
なぜなら当たり前の中にこそ、落とし穴は潜んでいるものだからです。
この言葉に思い当たることがあるエンジニアの方は一定数いらっしゃると思います。
だからこそ、自分をほんの少しだけ疑いつつ、当記事を読み進めていただけるとうれしいです。
では早速中身を見ていきましょう!
ファットモデルに陥るサイクル
ファットモデルに陥るパターンを理解していただくと、ファットモデルの根本的な解決方法がわかりやすくなると思いますので、はじめに書いていきたいと思います!
まずは下記の図「Railsがファットモデルに辿り着くまで」をご覧ください。
図をご覧いただくと「確かに」と思われる方も多いと思います。
RailsではSkinny Controller、Fat Modelという方針が推奨されていますので、MVCモデルで考えていただくと、おそらく以下の順序でファットモデルができあがって行くのだと思います。
解説
- まず、ほかのフレームワークもさわったことがない初心者の方が陥るのがファットビュー。ここは説明不要でしょう。APIモードで動かしている場合は関係ないですね。
- Railsに慣れ始めたエンジニアが陥るファットコントローラー。モデルに書くべき処理とコントローラで必要な処理の使い分けがうまくできずに、Controllerにロジックが集中するケース。このケースは割とススっと抜け出せると思います。
- 問題はここ。しっかり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を実行します。
Gemfilegem '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.rbmodule 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を実行します。
Gemfilegem '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.rbclass 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と切り離して考えられる処理は、切り離して個別でメンテナンスできるようにすることができます。
まとめ
記事を読んでいただきありがとうございました!
今回は「ファットモデルの回避方法」ということで記事を書かせていただきましたが、開発チームの方針、開発の優先度によって、課題に対してのアプローチ方法はもちろん異なってきます。
どちらかというと、最初に結論でも述べたとおり「ファットモデル自体は問題でなく、モデルに書くべきではない処理が多いことが問題」なわけです。
もっと抽象化すると「これってここにあるべきなのか?」という問いに対して思考できているかということになりそうですね。