YASD-TECH
YASD TECH
# Rails

extend ActiveSupport Concern の役割(included do と class_methods do)

投稿日:2026/8/5

更新日:2026/8/5

ttitleImage

extend ActiveSupport::Concern の役割(included do と class_methods do)

モデルからバリデーションを別モジュールへ切り出すと、だいたい先頭に extend ActiveSupport::Concern が付いてくる。これが何をしているのか、なぜ include ではなく extend なのかを整理しておく。

結論

  • extend ActiveSupport::Concern「このモジュールで included do / class_methods do を使えるようにする」宣言
  • included do のブロックは include 先のクラスのコンテキストで評価される。だから validates のようなクラスマクロが書ける。
  • 素の Ruby で書ける self.included(base) + base.class_eval の定型句を短くしただけで、やっていることは同じ。
  • extend はモジュール自身の能力拡張、include はクラスへの機能取り込み。主語が違う

何が問題なのか — module に validates は書けない

validatesクラスメソッド。素の module にそのまま書いても動かない。

ruby
module Article::Validators
  validates :title, presence: true   # → NoMethodError
end

module 自身に対して validates を呼んだことになるからだ。モジュールは ActiveRecord::Base ではないので、そんなメソッドは持っていない。

呼びたい相手は「モジュール」ではなく「include したクラス」。この主語のズレが、included do が存在する理由のすべて。


素の Ruby で書くとどうなるか

Ruby には「include された瞬間に呼ばれるフック」self.included がある。これを使えば Concern なしでも書ける。

ruby
module Article::Validators
  def self.included(base)          # base = include したクラス(= Article)
    base.class_eval do             # そのクラスのコンテキストで評価する
      validates :title, presence: true
    end
  end
end

動くが、モジュールを切り出すたびに self.included(base) + base.class_eval という定型句を毎回書くことになる。


Concern を使うとどう書けるか

ruby
module Article::Validators
  extend ActiveSupport::Concern

  included do
    validates :title, presence: true
  end
end
ruby
class Article < ApplicationRecord
  include Validators
end

included do ... end が上の定型句を肩代わりする。ブロックの中身は include したクラスのコンテキストで評価されるので、validatesArticle に対して呼ばれた形になる。

include したときに何が起きているかを追うとこうなる。

sequenceDiagram
    participant A as Articleクラス
    participant V as Validatorsモジュール
    participant C as Concernの仕組み

    A->>V: include Validators
    V->>C: included フックが発火
    C->>A: included do のブロックを class_eval
    Note over A: validates がクラスに対して呼ばれる

なぜ include ではなく extend なのか

ここが一番混乱しやすい。1つの機能を作るのに、2種類の取り込みが登場する

ruby
module Article::Validators
  extend ActiveSupport::Concern     # ← モジュール自身の能力を拡張する
  # ...
end

class Article < ApplicationRecord
  include Validators                # ← クラスに機能を取り込む
end
flowchart TD
    AS["ActiveSupport::Concern"] -->|extend| V["Article::Validators<br/>モジュール"]
    V -->|include| A["Article<br/>モデルクラス"]
    AS -.->|"included do / class_methods do<br/>が書けるようになる"| V
    V -.->|"validates がクラスに適用される"| A
誰に対して 何が起きるか
extend M そのオブジェクト自身(ここでは Validators モジュール) 特異メソッドとして生える。included / class_methods という DSL が Validators で使えるようになる
include M そのクラスのインスタンス インスタンスメソッドとして取り込まれる

Validators は「included do という書き方を使えるようになりたい」側なので extend。モデルは「バリデーションを取り込みたい」側なので include


class_methods do

included do と並ぶもう1つの主要機能。include 先にクラスメソッドを生やしたいときに使う。

ruby
module Searchable
  extend ActiveSupport::Concern

  class_methods do
    def search_by_title(keyword)
      where('title LIKE ?', "%#{keyword}%")
    end
  end
end
ruby
Article.search_by_title('Rails')   # → 使える

素の Ruby だと module ClassMethods ... end を定義して base.extend(ClassMethods) する必要がある。これも定型句の省略にすぎない。

ruby
# class_methods do と等価な素の書き方
module Searchable
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def search_by_title(keyword) = where('title LIKE ?', "%#{keyword}%")
  end
end

依存関係の自動解決(「Concern」の名前の由来)

Concern が単なる糖衣構文ではない部分がここ。モジュールが別のモジュールに依存する場合、素の Ruby だと included フックが正しく伝播しない。

ruby
module Timestampable
  extend ActiveSupport::Concern
  included { scope :recent, -> { order(created_at: :desc) } }
end

module Publishable
  extend ActiveSupport::Concern
  include Timestampable          # Concern 同士のネスト

  included { scope :published, -> { where.not(published_at: nil) } }
end

class Article < ApplicationRecord
  include Publishable            # recent も published も両方使える
end

素の module でこれをやると、Publishable を include したクラスに対して Timestampableincluded フックが発火しないTimestampablePublishable に include された時点でフックを消費してしまう)というのが古典的なハマりどころだった。ActiveSupport::Concern は依存関係を記録しておき、最終的に「本物のクラス」へ include されたタイミングでまとめて解決する。

ただし、単にバリデーションを1階層切り出すだけのケースではこの機能は使っていない。実質「included do を使うための宣言」と読んで差し支えない。


ハマりどころ

  • included do の中は「クラスのコンテキスト」self はモジュールではなく include 先のクラスを指す。定数やインスタンス変数の解決先を勘違いしやすい。
  • 通常のインスタンスメソッドは included do の外に書く。中に書くと class_eval 経由で定義されてしまい、モジュール経由の上書き(super での呼び出し)が効かなくなる。
ruby
module Article::Validators
  extend ActiveSupport::Concern

  included do
    validates :title, presence: true   # クラスマクロ → included do の中
  end

  def title_present?                   # インスタンスメソッド → 外
    title.present?
  end
end
  • include する側のファイルで名前空間を省略しすぎないArticle::ValidatorsArticle 内から include Validators と書けるのは Ruby の定数探索のおかげだが、別クラスから include するときはフルパスが要る。

参考

Index

  • extend ActiveSupport::Concern の役割(included do と class_methods do)
  • 結論
  • 何が問題なのか — module に validates は書けない
  • 素の Ruby で書くとどうなるか
  • Concern を使うとどう書けるか
  • なぜ include ではなく extend なのか
  • class_methods do
  • 依存関係の自動解決(「Concern」の名前の由来)
  • ハマりどころ
  • 参考