目次

Ruby on Railsのデザインパターンまとめ

この記事はRails Advent Calendar 2020の24日目の記事です。今月中旬に見てみたら24日だけ空いていたので参加を申し込みました。よろしくお願いいたします。

Ruby on Railsアプリケーションのディレクトリ構成としては、大きくmodelsとcontrollers、viewsがあります。ここにコードを書いていくことになります。実装が進むにしたがって問題になりやすいのが、Fat ModelやFat Controllerといったコードの肥大化に関する問題です。これは適切にデザインパターンを導入することで防ぐことができます。

この記事では、Railsにおけるデザインパターンとはなにか、その必要性やデザインパターンの一覧について書いていきます。なお、Railsアプリケーションの開発についてより詳しく学びたいときに参考になる本を次の記事でまとめています。あわせてご覧ください。

著者
Hiroki Zenigami

テクニカルライター。元エンジニア。プログラミング教室を作るのが目標です🚀 共著で「現場で使えるRuby on Rails 5」を書きました。

スポンサーリンク

Ruby on Railsにおけるデザインパターンとは

Railsにおけるデザインパターンとは、モデルやコントローラ、ビューに頻出する実装パターンを、オブジェクト設計の原則にもとづいて抽象化したパターンのことです。デザインパターンにしたがうことで、設計上の問題を防ぐことができます。

例:Formオブジェクト

たとえばFormオブジェクトというデザインパターンがあります。これはユーザーからの入力を整形・検証して永続化するという役割を持ちます。この処理はコントローラに書かれがちですが、このことはFat Controllerにつながりやすいです。

Formオブジェクトを導入することで、Fat Controllerの問題を防げます。またFormオブジェクト単体として再利用性が上がったり、テストが書きやすくなるといった利点もあります。

なぜデザインパターンが必要か

デザインパターンは、アプリケーションが大きくなるにしたがって起こりがちな設計上の問題を防ぐために必要になります。

代表的な問題がFat ModelやFat Controller、Fat Viewです。Railsはデフォルトでモデルやコントローラ、ビューを用意しています。用意されたこのファイル群だけにコードを記述していくと、オブジェクト指向設計の原則を守るのは難しいです。つまりコードが肥大化してしまいます。

こうなると拡張性や再利用性がなく、またテストも書きづらくなります。これを防ぐために、オブジェクト指向設計の原則に基づいてクラスを分割していく必要があります。

このクラス分割について、これまで開発者によって頻出パターンを抽象化されたのがデザインパターンです。Railsアプリケーションにデザインパターンを導入することで、Fat Modelをはじめとする設計上の問題を防ぐことができます。

スポンサーリンク

前提条件

まず、デザインパターンを導入するときの前提として、オブジェクト指向設計の原則を忠実に守る必要があります。たとえばSOLIDの原則などです。原則を守りつつ、まずはRailsが用意したモデルやコントローラ、ビューのレイヤーにコードを書いていきます。このとき純粋はRubyオブジェクトを積極的に活用します。

こうしてコードを書いていた結果、パターンが現れたときに適切なデザインパターンの導入を検討します。デザインパターンの導入ありきにならず、まずはオブジェクト指向設計の原則と向きあうことが前提となります。

これについては@joker1007氏の「俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ」でも詳しく書かれていますのであわせてお読みください。

Railsのデザインパターン一覧

Railsのデザインパターンについて、各責務とディレクトリ名、関連Gemがあればそれを書いています。このブログに別途記事を書いているパターンについては、記事へのリンクも貼っています。

Decoratorオブジェクト

モデルに対するビューのロジックをカプセル化する責務をもちます。ビューにはモデルに関連するロジックがよく登場しますが、ビューからモデルにアクセスすると肥大化の原因になり、また再利用性がなくテストも書きづらくなります。Decoratorオブジェクトでこの問題を防ぐことができます。

項目
ディレクトリapp/decorators
関連するGemactive_decorator

Deliveryオブジェクト

通知に関するロジックをカプセル化する責務をもちます。RailsにはデフォルトでActionMailerというしくみがあります。ただ、最近はメール以外にもSlackやLINEなどのチャットツールに通知することもよくあります。こういった通知に関する処理をラップするのがDeliveryオブジェクトです。

項目
ディレクトリapp/deliveries
関連するGemactive_delivery
スポンサーリンク

Formオブジェクト

ユーザーからの入力を整形・検証して永続化する責務をもちます。ユーザーからの入力を処理するのはコントローラの役割です。ただ、複雑な処理や複数の場所で行われる処理をコントローラに書くと、コードの肥大化といった問題の原因になります。Formオブジェクトで、この処理をカプセル化することができます。

項目
ディレクトリapp/forms
関連するGem-
関連記事Formオブジェクト

Interactorオブジェクト

ビジネスロジックをカプセル化する責務をもちます。Interactorオブジェクトは、アプリケーションの一つのみのビジネスロジックをもちます。ビジネスロジックを一つしかもたないため、再利用性があり、テストも書きやすいです。二つ以上の処理はInteractorオブジェクトを組み合わせることで実現します。

同じような役割としてServiceオブジェクトがあります。ただServiceオブジェクトは定義があいまいで、オブジェクト指向設計の原則が守られづらいという問題があります。Interactorは役割が明確で、またGemによるルールのおかげもあり設計の原則を守りやすいという利点があります。

項目
ディレクトリapp/interactors
関連するGeminteractor
関連記事Interactorオブジェクト

Policyオブジェクト

ビジネスルールをカプセル化する責務をもちます。たとえばユーザーの役割に応じて処理を実行する権限をもつかどうかを判断したりします。Policyオブジェクトがないと、コントローラやビューがルールに関するロジックであふれてしまうことになります。

項目
ディレクトリapp/policies
関連するGempundit

Queryオブジェクト

ActiveRecord::Relationに対して結合や絞り込み、ソートなどの操作を行い、Relationを返す責務をもちます。Relationに対する複雑な操作や再利用性のある操作を分割することで、再利用ができるなどの利点を得られます。ActiveRecord::Base継承クラスのスコープ経由で呼び出すことで、依存関係が明確になり、また返却されるクラスも自明になるという利点があります。

項目
ディレクトリapp/queries
関連するGem-
関連記事Queryオブジェクト
スポンサーリンク

Validatorオブジェクト

ActiveRecord::Base継承クラスのレコードを検証する責務をもちます。複数の場所で利用される検証ロジックを書くことで、再利用性などの利点を得られます。

項目
ディレクトリapp/validators
関連するGem-

Valueオブジェクト

値オブジェクトをカプセル化する責務をもちます。住所やメールアドレス、あるいは商品のレビューにおける星の数といった値オブジェクトを書くことで、再利用性などの利点を得られます。

項目
ディレクトリapp/values
関連するGem-
関連記事Valueオブジェクト

View Componentオブジェクト

ビューをコンポーネント単位でカプセル化する責務をもちます。再利用性のあるビューを、ビジネスロジックごとカプセル化することで、再利用性などの面で利点を得られます。

項目
ディレクトリapp/view_components
関連するGemview_component

まとめ

RailsアプリケーションのFat ModelやFat Controllerといった設計上の問題は、デザインパターンを導入することで防ぐことができます。ただ、その前提としてオブジェクト指向設計について学び、その原則にしたがって設計する必要があります。

原則を守った上でデザインパターンを導入することで、アプリケーションが大きくなっても秩序のあるコードベースを保つことができます。

あわせて読みたい

著者
Hiroki Zenigami

テクニカルライター。元エンジニア。プログラミング教室を作るのが目標です🚀 共著で「現場で使えるRuby on Rails 5」を書きました。

スポンサーリンク

ブログをはじめよう

技術ブログの始め方を、たくさんの画像で分かりやすく解説しました。これまでブログをやったことがない人でも、エンジニアにとって重要なブログを今日から始められます。