はじめに
本稿は super
について書いています。普段はインスタンスメソッドで super
を利用するシーンがあると思いますが、クラスメソッドだと混乱することがあったのでまとめになります。
super is 何?
super
をメソッドで呼び出すと、親クラスにある同名のメソッドを呼び出します。メソッドがなければ例外を起こします。
class Parent
def do(something)
p something
end
end
class Child < Parent
def do
super('something')
end
end
c = Child.new
インスタンスメソッドだと、ancestors
を使えばどのように探索をするかヒントになります。先の例だと Child
クラスの継承関係に Parent
クラスがあるので、そこからメソッドを探すことが出来ています。
もし、期待するメソッドが Parent
クラスになければ更に親のクラスを探索します。さらに親の Object
クラスを探索してなければ、Kernel
クラスを探索します。最後は BasicObject
クラスですね。
p Child.ancestors
#=> [Child, Parent, Object, Kernel, BasicObject]
ハマったこと
仕事で pundit の中を調査することがあり、次のような記述を見つけました。included
はモジュールがインクルードされたときのコールバックで base
クラスに対して更に拡張をする場合(クラスメソッドの追加など)によく使われます。
module PolicyExampleGroup
include Pundit::RSpec::Matchers
def self.included(base)
base.metadata[:type] = :policy
base.extend Pundit::RSpec::DSL
super # これ
end
end
さて、この super
はどのクラスで定義している included
メソッドでしょうか。はじめは ancestors
で調べればわかると思っていたのですがモジュールは親クラスを持たないのです。。。それでも例外にならない、なぜ?
p Pundit::RSpec::PolicyExampleGroup.ancestors
#=> [Pundit::RSpec::PolicyExampleGroup]
Ruby はクラスメソッドは特異クラスに定義されているので、クラスメソッドで super
を使うと探索は特異クラスの継承関係で行われます。
見つける対象になるメソッドはインスタンスメソッドであることに注意
p Pundit::RSpec::PolicyExampleGroup.singleton_class.ancestors
#=> [#<Class:Pundit::RSpec::PolicyExampleGroup>, Module, ..., Kernel, BasicObject]
今回は Module
クラスにあるインスタンスメソッドにマッチしたのでそれが実行されています。オーバーライドしなければ nil
を返すだけのメソッドなので削除しても良さそうですね
ひとまず、拙い英語で pull request を作成しましたがどうなることやら。。。
2022/10/22 更新
Varvet の方からコメントをもらいました。Module#included
が何もしないという共通認識は得られたんですが、リリースしてから長く時間がたったのでこの super
に依存してるものがあるかもしれないということで PR はクローズになりました。
ちょっと残念でしたが、まあしょうがないですよね