This page looks best with JavaScript enabled

Railsお手軽アーキテクチャ

 ·  ☕ 4 min read

Railsはデフォルトの構成で使われることが多いですが、やはりある程度の規模感になってくるとデフォルトの構成だと辛くなってくる場面もあると思います。
そういった場合にアーキテクチャを組み込みたくなりますが、Railsではそういったものは合わないと諦めている人も多いかと思います。

そこで自分がよく使用している構成を共有してみようと思います。個人的にはこれである程度の規模なら処理がどこにあるかわからない等の問題や、あるコードを修正することでどの程度影響があるわからないなどの問題が発生しづらくなると思います。
要素が2つ増える程度なので導入コストも低いです。

それでは説明していきます。

Railsのデフォルトの構成

とりあえずはRailsのデフォルトの構成を見てみましょう。
rails new のときに不必要そうなものは諸々削っておきます。

バージョン & インストール

1
2
3
4
5
6
7
$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

$ rails --version
Rails 6.1.1

$ rails new sample-app --skip-action-mailbox --skip-action-text --skip-action-cable --skip-webpack-install

appディレクトリ以下の構成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ tree -L 1 app
app
├── assets
├── controllers
├── helpers
├── javascript
├── jobs
├── mailers
├── models
└── views

よく見る形ですね。
各ディレクトリの説明は必要ないと思うので省きます。

おすすめの構成

次に自分がよく使用する構成を紹介します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ tree -L 1 app
app
├── assets
├── controllers
├── helpers
├── interactors
├── javascript
├── jobs
├── mailers
├── models
├── presenters
└── views

interactorsと、presentersを追加しました。
これだけでは何もわからないと思うので

  • models
  • presenters
  • interactors
  • controllers

の4つに関して、何をするのか、何をしてはいけないのかを説明します。

各コンポーネントであまりしないほうがいいことを紹介するのですが、Railsの特性上、それを強制するのは面倒になるのでコーディングルールとして守っていくものだと捉えてください。

models

Entity + Repositoryの役割を担うところになります。守るべきルールとしては where は必ずモデル内のみ、できれば scope 内で使用するようにしましょう。viewやcontrollerで where を使用しだすとカオスになります。

フックは処理が追いづらくなりやすい上に、なんでも屋になる傾向が強いと思っているので使用しません。
フックで処理していたようなものはinteractorで処理します。

presenters

表示用modelみたいなものです。

コード例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class UserPresenter
  def initialize(user)
    @user = user
  end

  def tel_with_hypen
    @user.tel.iikanji
  end

  def full_address
    "〒#{@user.zipcode} #{@user.address}"
  end
end

のような感じです。
大事なのはview側にはUserモデルのインスタンスではなく、UserPresenterモデルのインスタンスをわたして使用するようにしましょう。

  • 表示のためのロジックのテストがしやすくなる
  • Userモデルの何をviewで使用しているかがわかりやすくなる

等の利点があります。form_withなどの要素を使いたい場合はおとなしくUserモデルのインスタンスを渡すようにします。

interactors

call というpublic classを一つだけ持つUse caseを表すクラス群を定義します。

コード例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class PurchaseItem
  include App::Interactor

  def call(user, item_id)
    item = Item.find_by(item_id)

    return nil unless item

    purchase = user.purchase(item)
    purchase.save!

    PurchaseMailer.with(user, purchase).deliver_later

    PurchasePresenter.new(purchase)
  end
end

Modelで定義したメソッドや、メール送信処理、トランザクションなどを駆使してUse caseを達成するための処理を記述します。アプリケーションにある主要な機能は基本的にこのクラスから処理を始める考えていいでしょう。そうすることで、ある機能にバグがあったり、追加機能が必要だったりしたときにこのクラスだけでだいたい何をしているのかがわかるようになります。

presenterがある場合にはここでpresenterに必要なデータを詰めて返します。

controllers

データを受け取ってInteractorに渡し、Interactorから返ってきたデータをviewにわたすための場所です。

コード例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class PurchaseController
  def create
    @presenter = PurchaseItem.new.call(current_user, params[:id])

    if @presenter
      render :complete
    else
      render :edit
    end
  end
end

まとめ

Railsである程度の規模感でも耐えられるような構成を紹介してみました。基本的には責任範囲をちゃんと定めることによって、コードが散らばることを防ぐための構成です。当然これでは不足する場合もあると思いますのでその場合にはまた責任を分割して適切な名前付けをしてあげる必要があると思います。

Share on

ippachi
WRITTEN BY
ippachi
Software Developer