0
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のクラス定義は実行可能コード!特異メソッド・特異クラスの秘密に迫る!

0
Last updated at Posted at 2026-03-20

Rubyのクラス定義を深く理解する

概要

この記事は、「メタプログラミングRuby 第2版」第5章「クラス定義」を読み学習した内容を個人学習用にまとめ直したものです。

Rubyのクラス定義は単なる設計図ではなく、実行可能なコードです。本記事では、カレントクラス、特異メソッド、特異クラス、クラスマクロ、メソッドラッパーなど、クラス定義にまつわる技法・仕組みについて解説します。

クラス定義

クラス定義の基本

クラス定義はメソッドを定義する場所ではなく、実際にはそれ以外のあらゆるコードを記述できる。

また、メソッドやブロックと同じように、クラス定義も最後に評価された式の値を返す。

result = class MyClass
  self
end

result # => MyClass

上記のように、クラス定義の中ではクラスがカレントオブジェクトselfとなる。

クラスとモジュールはそれぞれClassクラスとModuleクラスのオブジェクトである。

カレントクラス

Rubyのプログラムは、常にカレントオブジェクトselfをもち、同様に常にカレントクラス(あるいはカレントモジュール)も持っている。

  • プログラムのトップレベルでは、カレントクラスはmainのクラスとなるObjectクラスとなる
  • classキーワードでクラスをオープンすると、そのクラスがカレントクラスになる
    • クラス定義の中ではカレントオブジェクトselfはそのクラス自身なので、カレントクラスと同じとなる
  • メソッドの中ではカレントオブジェクトのクラスがカレントクラスになる

class_eval

Module#class_evalを使用することでレシーバのクラスをオープンし、そのコンテキストでブロックを評価できる。

def add_method_to(a_class)
  # 引数のクラスをオープンしメソッドを定義する
  a_class.class_eval do
    def m
      "Hello!"
    end
  end
end 

add_method_to(String)
"abc".m # => "Hello!"

クラスインスタンス変数

クラス定義の中でそのクラス自身となるClassオブジェクトがselfとなる場所に定義されているインスタンス変数をクラスインスタンス変数と呼ぶ。

クラスインスタンス変数とそのクラスから作成されたオブジェクトのインスタンス変数は別物なので注意。

class MyClass
  # MyClassというClassオブジェクトのインスタンス変数(クラスインスタンス変数)
  # MyClassから作成されるオブジェクトのインスタンス変数ではない
  @my_var = 1

  def self.read
    @my_var # このクラス自身のクラスインスタンス変数を読み取る
  end

  def write
    @my_var = 2 # クラス定義から作成したオブジェクトの属性を更新
  end

  def read
    @my_var # クラス定義から作成したオブジェクトの属性を読み取る
  end
end

obj = MyClass.new
obj.read # => nil
obj.write
obj.read # => 2
MyClass.read # => 1

特異メソッド

Rubyでは特定のオブジェクトにだけメソッドを追加できる。これを特異メソッドと呼ぶ。

str = "just a regular string"

# strオブジェクトにだけ特異メソッドを定義
def str.title?
  self.upcase == self
end

str.title? # => false
# 別のStringオブジェクトには特異メソッドは存在しない
"another string".title? # => NoMethodError

クラスメソッド

クラスはClassオブジェクトであり、クラスメソッドはそのClassオブジェクトの特異メソッドとなる。

class MyClass
  # 以下の2つは同じ意味
  def self.hello
    "Hello from #{self}!"
  end
end

def MyClass.goodbye
  "Goodbye from #{self}!"
end

MyClass.hello   # => "Hello from MyClass!"
MyClass.goodbye # => "Goodbye from MyClass!"

クラスマクロ

アクセサを一気に定義できるModule#attr_*メソッドは実態はただのメソッド呼び出し(Moduleのインスタンスメソッド)だが、見た目が言語組み込みのキーワードのように記述される。

このようなメソッドのことをクラスマクロと呼ぶ。

class MyClass
  # 以下はキーワードではなく、Moduleのインスタンスメソッド
  attr_accessor :my_attribute
end

クラスマクロの活用例:

メソッド名を変更したいが、旧メソッド名も一定期間残して警告を出したい場合、deprecateクラスマクロを自作することで宣言的に非推奨化できる。

class Module
  # 古いメソッド名を受け取って動的定義し、その中で警告メッセージとともに新メソッドを呼び出す
  def deprecate(old_method, new_method)
    define_method(old_method) do |*args|
      warn "警告: #{old_method}は非推奨です。#{new_method}を使ってください。"
      send(new_method, *args)
    end
  end
end

class Book
  def title
    "メタプログラミングRuby"
  end

  # 自作クラスマクロ: GetTitleを非推奨にしtitleへ転送する
  deprecate :GetTitle, :title
end

Book.new.GetTitle
# => 警告: GetTitleは非推奨です。titleを使ってください。
# => "メタプログラミングRuby"

Railsではクラスマクロが多用されている。以下はその代表例。

クラスマクロ 役割
attr_accessor :name ゲッター・セッターの自動生成
has_many :posts アソシエーション定義
validates :name, presence: true バリデーション定義
before_action :authenticate! コールバック定義
scope :active, -> { where(active: true) } スコープ定義

これらはすべてクラス定義の中で呼ばれるクラスメソッドであり、内部ではメソッドの動的生成やコールバック・バリデーションの登録などを行っている。

特異クラス

そのオブジェクトのみが固有で持つ特異メソッドは、そのオブジェクトのクラスや親クラスには定義が存在せず、オブジェクトの裏に存在する**特異クラス(メタクラス、シングルトンクラス)**という特別なクラスに定義される。

特異メソッドとは、特異クラスに定義されたインスタンスメソッドに他ならない。

特異クラスは以下のような特徴を持つ。

  • インスタンスを一つしか持てない
  • class <<Object#singleton_classという特別な構文を使わなければ通常は不可視
obj = Object.new

singleton_class = class << obj # 特異クラスのスコープにアクセスする構文
  self
end

singleton_class.class # => Class

# または

obj.singleton_class # => #<Class:#<Object:0x331df0>>

メソッド探索における特異クラスと継承の関係

Rubyがメソッドを探索する際、特異クラスはそのオブジェクトの通常のクラスよりも先に探索される。

オブジェクトの場合

obj.hello を呼ぶと...

obj → #<Class: obj>(特異クラス) → MyClass → Object → Kernel → BasicObject
       ↑ まずここを探す          ↑ 次にここ
class MyClass
  def hello
    "通常のhello"
  end
end

obj = MyClass.new

def obj.hello
  "特異メソッドのhello"
end

obj.hello # => "特異メソッドのhello"(特異クラスが先に探索される)

クラスの場合

クラスメソッドはClassオブジェクトの特異メソッドであるため、クラスの特異クラスに定義される。

MyClass.my_method を呼ぶと...

#<Class: MyClass>(特異クラス) → #<Class: Object> → #<Class: BasicObject> → Class → Module → Object
 ↑ クラスメソッドはここにいる
# クラスメソッドの定義方法は3つ
def MyClass.my_method
  "クラスメソッド"
end

class MyClass
  def self.my_method
    "クラスメソッド"
  end
end

class MyClass
  class << self # 特異クラスをオープンして定義
    def my_method
      "クラスメソッド"
    end
  end
end

MyClass.singleton_class.instance_methods(false) # => [:my_method]

二つの継承チェーン

Rubyの内部には二つの継承チェーンが並行して存在し、レシーバに応じてどちらを辿るかが決まる。特異クラスの継承により、クラスメソッドも親クラスから引き継がれる。

インスタンスメソッド用(obj.hello):
  #<Class: obj> → Child → Parent → Object → Kernel → BasicObject

クラスメソッド用(Child.hello):
  #<Class: Child> → #<Class: Parent> → #<Class: Object> → ... → Class → Module → Object
class Parent
  def self.greet
    "Hello from Parent"
  end
end

class Child < Parent
end

Child.greet # => "Hello from Parent"(#<Class: Parent> から継承)

継承の関係をまとめると以下の通り。

  • オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラスである
  • クラスの特異クラスのスーパークラスは、クラスのスーパークラスの特異クラスとなる

モジュールからクラスメソッドをincludeする方法

モジュールにクラスメソッドを定義して単にクラスにincludeするのみでは、クラスメソッドとしてクラスに定義することはできない。

モジュールのincludeで手に入るのは、モジュールのインスタンスメソッドのみだからである。

module MyModule
  def self.my_method
    "hello"
  end
end

class MyClass
  include MyModule
end

MyClass.my_method # NoMethodError!

クラス拡張

モジュールのメソッドをクラスメソッドとしてincludeするには、以下の手順を踏む。

  1. モジュールでメソッドを単なるインスタンスメソッドとして定義する
  2. 挿入したいクラスの特異クラスでモジュールをincludeする

特異クラスのインスタンスメソッド == 特異クラスのオブジェクトの特異メソッド(クラスメソッド)なので、
この方法で定義できる。この手法をクラス拡張と呼ぶ。

module MyModule
  def my_method
    "hello"
  end
end

class MyClass
  # 特異クラスをオープンしてincludeする
  class << self
    include MyModule
  end
end

MyClass.my_method # "hello"

オブジェクト拡張

クラス拡張の手法は通常のオブジェクトにも適用できる(オブジェクト拡張)。

module MyModule
  def my_method
    "hello"
  end
end

obj = Object.new

class << obj
  include MyModule
end

obj.my_method          # => "hello"
obj.singleton_methods  # => [:my_method]

extend

クラスやオブジェクトを拡張するために特異クラスをオープンするのはあまり自然なことではないので、クラスやオブジェクトを拡張する専用のショートカットメソッドObject#extendが存在する。

module MyModule
  def my_method
    "hello"
  end
end

obj = Object.new
obj.extend MyModule
obj.my_method          # => "hello"

class MyClass
  extend MyModule
end

MyClass.my_method # => "hello"

メソッドラッパー

アラウンドエイリアス

Module#alias_methodでメソッドに別名をつけることができる。

class MyClass
  def my_method
    "hello"
  end

  # 新しい名前を先に、元の名前を後に書く
  # シンボルか文字列で渡す
  alias_method :m, :my_method
end

obj = MyClass.new
obj.my_method  # => "hello"
obj.m          # => "hello"

alias

aliasはRubyのキーワード、alias_methodModuleのメソッドである。alias_methodは動的に変数を渡せるがメタプログラミング向き。トップレベルスコープではModuleのメソッドであるalias_methodは使えないため、aliasを使う必要がある。

def greet = "hello"

alias original_greet greet        # OK(キーワードなのでどこでも使える)
# alias_method :original_greet, :greet  # NoMethodError(Moduleのメソッドなので使えない)

さらに、以下の手順を踏むことで、メソッドを同じ名前で再定義し、動作を拡張することができる。
この手法をアラウンドエイリアスと呼ぶ。

  1. メソッドにエイリアスをつける
  2. メソッドを再定義する
  3. 新しいメソッドから古いメソッドを呼び出す
class MyClass
  def greet
    "こんにちは"
  end

  # 1. メソッドにエイリアスをつける
  alias_method :original_greet, :greet

  # 2. メソッドを同じ名前で再定義する
  def greet
    # 3. 新しいメソッドから古いメソッド(エイリアス)を呼び出す
    result = original_greet
    "★ #{result} ★"
  end
end

obj = MyClass.new
obj.greet          # => "★ こんにちは ★"
obj.original_greet # => "こんにちは"

Refinementsラッパー

Refinementsを使うと、refineブロック内でメソッドを再定義し、superで元のメソッドを呼び出すことでメソッドラッパーを作ることができる。

class MyClass
  def greet
    "こんにちは"
  end
end

module GreetWrapper
  refine MyClass do
    def greet
      # superで元のMyClass#greetを呼び出す
      result = super
      "【 #{result} 】"
    end
  end
end

# usingしなければ元のまま
MyClass.new.greet # => "こんにちは"

# usingするとそのスコープ以降でラッパーが有効になる
using GreetWrapper
MyClass.new.greet # => "【 こんにちは 】"

Refinementsはrefineでクラスを拡張し、usingで有効化するスコープ限定の仕組みである。アラウンドエイリアスと違いグローバルに影響しない。usingはファイルやモジュール定義のスコープ単位で有効になり、メソッド内では使用できない。

Prependラッパー

prependでモジュールをインクルーダー(クラス)の直後に挿入し、superで継承チェーン上のインクルーダーのメソッドを呼び出すことでラッパーにできる。

Refinementsラッパーやアラウンドエイリアスよりも明示的で綺麗な手法とされている。

class MyClass
  def greet
    "こんにちは"
  end
end

module GreetWrapper
  def greet
    # superで継承チェーン上の次のメソッド(MyClass#greet)を呼び出す
    result = super
    "《 #{result} 》"
  end
end

MyClass.prepend(GreetWrapper)

MyClass.new.greet # => "《 こんにちは 》"

3つのメソッドラッパー手法の比較

手法 仕組み 特徴
アラウンドエイリアス alias_methodで退避し再定義 シンプルだが再実行に弱い
Refinementsラッパー refine+usingでスコープ限定 安全だがスコープ制約がある
Prependラッパー prependで継承チェーンに挿入 最もクリーンで推奨される手法

まとめ

  • Rubyのクラス定義は実行可能なコードであり、最後に評価された式の値を返す。クラス定義の中ではselfがそのクラス自身となる
  • Module#class_evalを使うことで、変数に格納されたクラスをオープンしてメソッドを動的に定義できる
  • 特異メソッドは特定のオブジェクトだけが持つメソッドであり、オブジェクトの裏に存在する特異クラスに定義される
  • メソッド探索では特異クラスが通常のクラスより先に探索され、クラスメソッドもクラスの特異クラスに定義された特異メソッドである
  • メソッドラッパーにはアラウンドエイリアス・Refinements・prependの3手法があり、prependが最もクリーンで推奨される

参考文献

この記事は以下の情報を参考にして執筆しました。

0
0
0

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
0
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?