1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ruby の extend と特異クラス with Sinatra

1
Last updated at Posted at 2026-06-03

今まで、extend はクラス定義の中で呼ぶパターンにしか出会ってこなかった。

module M1
  def bark
    "bow wow"
  end
end

class C1
  extend M1
end

これをすると、

> C1.bark
=> "bow wow"

という具合。これだけを見ると、「extend はクラスメソッドを追加するもの」だったので、そう認識していた。

しかし、sinatraのソースコードに以下のような記述があった。

ここではまず、クラス定義内ではなくファイルのトップレベルで extend を呼んでいた。

最初に、Sinatra::Delegator とは、getpatch などの sinatra が提供するメソッドの呼び出しを、sinatra の主要クラスである Sinatra::Base クラスに移譲するモジュールらしい。つまり、このモジュールを mixin すると、sinatra が提供する getpatch などのメソッドをそのオブジェクトで呼び出せるようになるということ。

ファイルのトップレベルで extend するとどうなるのか?

ファイルのトップレベルで extend を直接呼んでいるということは、ファイルのトップレベルでself.extend を呼んでいるということ。そして、ファイルのトップレベルにおける self は、コードの実行主体である「 main オブジェクト」とのこと。そして、main オブジェクトはObject クラスのインスタンスらしい。

# こういうこと
<mainオブジェクト(Objectクラスのインスタンス)>.extend Sinatra::Delegator

インスタンスに extendってできるの?

今まで extend は、「モジュールに定義したメソッドをクラスメソッドとして挿入するためのもの」だと思っていた。それをクラスじゃなくてインスタンスに対して呼ぶとどうなるんだ?と思った。

extend は、「呼び出し元のオブジェクトの 特異クラス にモジュールを mixin すること」らしい。

全ての Ruby オブジェクトは何らかのクラスのインスタンスであり、自身のクラスは #class メソッドで呼び出せる。

それと同時に、「自身のみと一対一で対応するクラス」を #singleton_class メソッドで呼び出せるらしい1。それが特異クラス。

irb(main):031* class C2
irb(main):032> end
=> nil
irb(main):033> C2.class
=> Class
irb(main):034> C2.singleton_class
=> #<Class:C2>
irb(main):035* class C3
irb(main):036> end
=> nil
irb(main):037> C3.singleton_class
=> #<Class:C3>

#<Class:C2> #<Class:C3>が特異クラスということらしい。

あるオブジェクト a に対して extend を呼び出すと、そのオブジェクトの特異クラス(a.singleton_class)にモジュールを mixin する。そうすると、その特異クラスのシングルトンインスタンスである a でそのモジュールのメソッドや変数が使えるようになる。

一方で、そのオブジェクトの通常クラス(a.class)の他のインスタンスでは、そのモジュールのメソッドや変数は使えない。あくまで、a の特異クラスに mixin されているだけなので。

main オブジェクトに extend すると?

main オブジェクトはファイルのトップレベルに位置しているので、mainオブジェクトに extend すると、getpatch などのメソッドをファイルのトップレベルで呼べるようになる。

これによって get などを DSL っぽく扱える、ということだった。なるほど。

なんで include じゃダメなのか

コメントを見ると、「include だとモジュールを Object オブジェクトに入れてしまう。extend なら main オブジェクトだけにしか展開しない。」とある。

トップレベルで include を呼ぶと、Object クラスで include したのと同じことになるらしい。これは、mainオブジェクトに個別に includeメソッドが定義されており、これが内部で Object クラスへの include として処理してくれるかららしい。

全てのオブジェクトは Object クラスのインスタンスなので、include することによってあらゆるオブジェクトからそのメソッドが呼べるようになってしまい、あらゆるものを汚染してしまうとのこと。

extend を使うことによって、mainオブジェクトだけの特異クラスに mixin するので、他のオブジェクトは汚染せず、main オブジェクトだけでモジュールのメソッドが使えるようになるとのこと

まとめ・学んだこと

  • extend は、呼び出したオブジェクトの特異クラスに include を書くのと一緒
  • 特異クラスは、そのオブジェクトをシングルトンインスタンスとして生成するそのオブジェクト専用のクラスで、全てのオブジェクトに対して存在する
  • DSL っぽい構文は main オブジェクトへの mixin によって実現されていた
  • Ruby ではコードを実行するのもオブジェクト、全部がオブジェクト
  • extend は全ての Ruby オブジェクトに対して呼べるが、include はクラスとモジュールとmainオブジェクトに対してしか呼べない2
    • 正確には Module クラスのインスタンスでしか呼べない
    • モジュールは Module クラスのインスタンスで、クラスは Module クラスを継承した Class クラスのインスタンス
  1. 元々は、「自身をシングルトンインスタンスとして生成する自分専用のクラス」と記述していましたが、コメントにてご指摘をいただき不正確さを確認したため、修正しました🙏

  2. 元々は、「extend は全ての Ruby オブジェクトに対して呼べるが、include はクラスやモジュールにしか呼べない」と記述していましたが、コメントにてご指摘いただき、mainオブジェクトにもincludeメソッドが特別に定義されている ことを確認したため、不正確さ解消のために修正しました 🙏

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?