ActiveSupport::Concern の使い方
概要
Rails では ActiveSupport::Concern
を使ってモジュールを作成しているのをよくみますね。
でも module のメソッドについてあまり理解せずに使っている人も多いかと思います。
今回は そもそも Ruby での module の扱い方法に触れ、
ActiveSupport::Concern
を使うとどのように書き方が変わるのかを説明します。
環境
ruby '3.1.2'
'rails', '~> 7.0.8'
ActiveSupport::Concern を使わずに module を使用する
インスタンスメソッドを実行する
モジュールはこちら。
module M
def aaa
"aaa です"
end
end
クラスにインクルードします。
class C
include M
def ddd
"ddd です"
end
end
rails console での実行結果。
モジュールで定義したaaa
メソッドも クラスで定義したddd
メソッドも、
インスタンスメソッドとして実行できていますね。
> C.new.aaa
=> "aaa です"
> C.new.ddd
=> "ddd です"
クラスメソッドを実行する
モジュールに特異メソッドを定義しても、インクルード先のクラスでは実行できない。
モジュールがこちら
module M
def self.aaa
"aaa です"
end
end
モジュール M では、クラスメソッドのように実行できます。
しかし、インクルード先の C では、
「そんなメソッド定義されてねーよ」とエラーしてしまいます。
> M.aaa
=> "aaa"
> C.aaa
(irb):90:in `<main>': undefined method `aaa' for C:Class (NoMethodError)
インクルード先のクラスでモジュールの特異メソッドを実行する方法
主に以下の2つがあります。
-
extend
で mix-in する - モジュールの読込み時に特異メソッドとして定義する
extend で mix-in する
モジュールではインスタンスメソッドを定義します。
module M
def aaa
"aaa です"
end
end
定義したモジュールのメソッドを、特異メソッド(クラスメソッド)として実行できるように、
extend で mix-in をする
class C
extend M
def ddd
"ddd です"
end
end
これで、モジュールのメソッドは特異メソッドとして、
クラスで定義しているインスタンスメソッドはそのままインスタンスメソッドとして実行できます。
> C.aaa
=> "aaa です"
> C.new.ddd
=> "ddd です"
モジュールの読込み時に特異メソッドとして定義する
先程の例では、モジュールに定義した普通のメソッドと、特異メソッドの両方をそのまま使うことはできませんよね。
インクルード先では、
モジュールで定義したメソッドはインスタンスメソッドとして、
特異メソッドはクラスメソッドとして実行したいはずです。
そんなときは以下のように書きます。
module M
def aaa
"aaa です"
end
module ClassMethods
def bbb
"bbb です"
end
end
def self.included(base)
base.extend ClassMethods
end
end
モジュール内でモジュールを定義しているのは気持ち悪い気がしますが、
その定義したモジュールを、インクルード先で extend するようにしています。
extend は先ほど説明した通り、モジュールで定義したメソッドをクラスメソッドとして読み込ませるものでしたね。
def self.included(base)
base.extend ClassMethods
end
クラスの方では include で mix-in します。
class C
include M
def ddd
"ddd です"
end
end
> C.new.aaa
=> "aaa です"
> C.bbb
=> "bbb です"
> C.new.ddd
=> "ddd です"
ちらっと出てきましたが、included do ~ end
は、 C
クラスにインクルードされた直後に実行されます。
module M
def self.included(base)
base.class_eval do
p "include されました"
end
end
end
class C
include M
end
> C
"include されました"
=> C
ActiveSupport::Concern を使って module を使用する
今までのことを整理すると、
module で定義したメソッドをインスタンスメソッドとして、
特異メソッドをクラスメソッドとして実行するには、以下のようなコードになります。
module M
def aaa
"aaa です"
end
module ClassMethods
def bbb
"bbb です"
end
end
def self.included(base)
base.extend ClassMethods
end
end
class C
include M
end
ここまでのことを理解していないと、パッと見てもちょっと分かりづらいですよね。
これを、ActiveSupport::Concern
を使うと、以下のようになります。
module M
extend ActiveSupport::Concern
def aaa
"aaa です"
end
class_methods do
def bbb
"bbb です"
end
end
end
class C
include M
end
class_methods
メソッドのブロック内でメソッドを定義することで、
インクルード先でクラスメソッドとして使用することができます。
かなりスッキリと見やすくなったのではないでしょうか。
Rails でよく見かける Concern
Rails でよく見るのは、モデルの validation や association を共通化する例ですね。
has_many
や scope
などを included do ~ end
で囲っているのをよく見ます。
module HogeModelModule
extend ActiveSupport::Concern
included do
has_many :comments
scope :active, -> { where(is_active: true) }
end
end
Discussion
わかりやすかったです!
これで理解できました!!
コメントありがとうございます!
良かったです!そう言っていただけると嬉しいです!