はじめに
Railsで開発をしていると、共通の処理を複数のModelやControllerへコピー&ペーストしてしまい、「同じようなコードがあちこちにある」という状況になることがあります。
そんなときに役立つのがConcernです。
この記事では、ファットモデル・ファットコントローラーを避けるための代表的な設計パターンの一つであるConcernについて整理してみます。
Concernとは
Concernは、Rails標準の ActiveSupport::Concern を利用して、ModelやControllerで共通の処理をまとめるための仕組みです。
共通のメソッドをまとめるだけであればRuby標準のModuleでも実現できますが、extend ActiveSupport::Concern を利用することで、included do や class_methods do を使いながら共通の設定やメソッドを分かりやすく定義できます。
複数のControllerで共通して利用する before_action やprivateメソッド、複数のModelで共通するスコープやバリデーション、メソッドなどを切り出す用途で利用されます。
この記事では、ファットモデル化を防ぐ手段の一つとして利用されるModel Concernを例に紹介します。
例えば、Article と News のように、複数のModelが同じ「公開・非公開」の振る舞いを持つ場合、共通の処理をConcernとして切り出すことができます。
実装例
# app/models/concerns/publishable.rb
module Publishable
extend ActiveSupport::Concern
included do
scope :published, -> { where(published: true) }
validates :published, inclusion: { in: [true, false] }
end
class_methods do
def recent
published.order(created_at: :desc)
end
end
def publish!
update!(published: true)
end
end
Concernを利用したいModelでは、include Publishable を書くだけで、公開機能に関する共通のスコープやバリデーション、メソッドを利用できます。
# app/models/article.rb
class Article < ApplicationRecord
include Publishable
end
# app/models/news.rb
class News < ApplicationRecord
include Publishable
end
これにより、Article と News のどちらでも同じ機能を利用できます。
Article.published
News.published
Article.recent
News.recent
article.publish!
news.publish!
Concernは便利ですが、「とりあえず共通処理をConcernへ移す」という使い方をすると、コードの見通しが悪くなったり、責務が分かりづらくなる可能性もあります。
そのため、
- 本当に複数のクラスで再利用される処理なのか
- 共通の責務と言えるのか
を意識して切り出した方が、後から読みやすいコードになりやすいと思います。
その他の設計パターン
Railsでは、ファットモデルやファットコントローラーを避けるために、Concern以外にもさまざまな設計パターンがあります。
| パターン | 主な用途 |
|---|---|
| Service Object | ユースケース単位の業務処理をまとめたいとき |
| Concern | 複数のModelやControllerで共通の責務を切り出したいとき |
| Form Object | フォーム入力や複数モデルのバリデーション・保存をまとめたいとき |
| Query Object | 検索条件やJOINが複雑になり、scopeだけでは見通しが悪くなったとき |
| Policy Object | 認可ロジックを分離したいとき |
| Decorator / Presenter | View表示用のロジックをModelから切り離したいとき |
| PORO(Plain Old Ruby Object) | ActiveRecordを継承しない通常のRubyクラスとして、ドメインロジックを切り出したいとき |
今回はConcernに絞って紹介しましたが、他の設計パターンについても、機会があれば整理して記事にしてみたいと思います。