Ruby on RailsのConcerns(コンサーン)は、複数のコントローラーやモデルで共有したい処理をモジュールとして切り出す仕組みです。ApplicationControllerにすべての共通処理を書くと肥大化してしまいますが、Concernsを使えば機能ごとにモジュールを分離し、必要なコントローラーにだけincludeできます。この記事では、Concernsの作成方法と活用パターンを解説します。
基本的な使い方
Concernsはapp/controllers/concerns/ディレクトリにモジュールとして定義します。ActiveSupport::Concernをextendすることで、includedブロック内にコールバックなどを記述できます。
module Authenticatable
extend ActiveSupport::Concern
included do
before_action :require_login
end
private
def require_login
unless current_user
redirect_to login_path, alert: "ログインが必要です"
end
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
endこのConcernを使いたいコントローラーでincludeするだけで、認証機能が有効になります。
class PostsController < ApplicationController
include Authenticatable
def index
@posts = Post.all
end
endApplicationControllerとの違い
ApplicationControllerに定義した処理はすべてのコントローラーに適用されますが、Concernsはincludeしたコントローラーにだけ適用されます。
| 方法 | 適用範囲 | 使い分け |
|---|---|---|
| ApplicationController | すべてのコントローラー | 全画面で必要な処理(ロケール設定など) |
| Concerns | includeしたコントローラーのみ | 特定の機能を持つコントローラー群で共有する処理 |
実践的なConcernの例
ページネーション機能をConcernとして定義する例です。
module Paginatable
extend ActiveSupport::Concern
private
def page_number
(params[:page] || 1).to_i
end
def per_page
(params[:per_page] || 20).to_i
end
def paginate(scope)
scope.offset((page_number - 1) * per_page).limit(per_page)
end
endclass PostsController < ApplicationController
include Paginatable
def index
@posts = paginate(Post.order(created_at: :desc))
end
endモデル用のConcerns
コントローラーだけでなく、モデルでもConcernsを使えます。app/models/concerns/に定義します。
module SoftDeletable
extend ActiveSupport::Concern
included do
scope :active, -> { where(deleted_at: nil) }
scope :deleted, -> { where.not(deleted_at: nil) }
end
def soft_delete
update(deleted_at: Time.current)
end
def restore
update(deleted_at: nil)
end
def deleted?
deleted_at.present?
end
endclass Post < ApplicationRecord
include SoftDeletable
# Post.active で削除されていないレコードのみ取得
# Post.deleted で削除済みレコードのみ取得
endclass_methodsの定義
Concernにクラスメソッドを定義する場合は、class_methodsブロックを使います。
module Searchable
extend ActiveSupport::Concern
class_methods do
def search(query)
where("title LIKE ? OR content LIKE ?", "%#{query}%", "%#{query}%")
end
end
endConcernは機能単位で分割するのがベストプラクティスです。1つのConcernに複数の異なる責務を持たせると、結局肥大化してしまいます。「認証」「ページネーション」「論理削除」など、明確な単一機能ごとに分けましょう。
Concernsの多用はコードの依存関係を複雑にする可能性があります。Concernが他のConcernに依存する場合や、includeの順序が重要になる場合は、設計を見直すことを検討してください。
まとめ
- Concernsは
ActiveSupport::Concernをextendしたモジュールとして定義する includedブロック内にコールバックやスコープを記述できるincludeしたコントローラーやモデルにだけ機能が適用される- コントローラー用は
app/controllers/concerns/、モデル用はapp/models/concerns/に配置 class_methodsブロックでクラスメソッドを定義できる- 1つのConcernには単一の責務を持たせ、機能ごとに分割する