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?

alias_method_chain ― メタプログラミングRubyで読み解くアラウンドエイリアスの盛衰

0
Last updated at Posted at 2026-04-18

alias_method_chain

概要

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

Railsにはかつてalias_method_chainというメソッドが存在し、既存メソッドの前後に処理を挟む「アラウンドエイリアス」パターンを簡潔に実現していました。本記事では、メタプログラミングの観点から、alias_method_chainがどのような課題を解決したのか、そしてシンプルなオーバーライドやRuby 2.0で導入されたModule#prependによってどのように役割を終えていったのかを読み解きます。

alias_method_chain

冗長なエイリアス設定

以下のようなモジュールのメソッドに新機能を追加する場合、「メソッド」「メソッド_without_機能」「メソッド_with_機能」という命名規則に従った3つのメソッド名を用意し、エイリアスで旧名メソッドと「メソッド_without_機能」の参照先を切り替えるのがRailsにおいては一般的。

module Greetings
  def greet
    "hello"
  end
end
class MyClass
  include Greetings

  def greet_with_enthusiasm
    "Hey, #{greet_without_enthusiasm}!"
  end

  alias_method :greet_without_enthusiasm, :greet # 元の内容のメソッド処理を呼び出す場合のエイリアス
  alias_method :greet, :greet_with_enthusiasm # 元の名前を呼び出した場合に、新しい機能を呼び出すエイリアス
end

MyClass.new.greet # => "Hey, hello!"
MyClass.new.greet_without_enthusiasm # => "hello"

Railsでは一般的な手法だが、この方法だと上記と同じような冗長な記述が機能を追加したいメソッドごとに複製されることとなる。

この問題を解決するために用意されたのが、alias_method_chainである。

alias_method_chainの中身

alias_method_chainの実装は、先ほどの冗長なエイリアス設定をメタプログラミングで自動化したものである。

class Module
  # ターゲットとなるメソッドと機能追加メソッドの名前を引数として受け取る
  def alias_method_chain(target, feature)
    # メソッド名の末尾に`?`、`!`、`=`がある場合にも対応するため、`sub`で記号を分離している
    aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ""), $1
    yield(aliased_target, punctuation) if block_given?

    # target_with_feature, target_without_featureを定義する
    with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
    without_method = "#{aliased_target}_without_#{feature}#{punctuation}"

    # エイリアスの設定
    alias_method without_method, target
    alias_method target, with_method

    # 元メソッドのアクセス修飾子を保持する
    # without_methodは元メソッドのコピーなので、その可視性を調べて差し替え後のtargetに再適用する
    case
    # 元メソッドがpublic, protect, privateだったら同じ可視性に戻す
    when public_method_defined?(without_method)
      public target
    when protected_method_defined?(without_method)
      protected target
    when private_method_defined?(without_method)
      private target
    end
  end
end

alias_method_chainが実際にRailsでどう使われていたかの例として、ActiveRecord::Validationsの抜粋を示す。saveメソッドにバリデーション機能を追加するために使用されていた。

module ActiveRecord
  module Validations
    def self.included(base)
      base.class_eval do
        # saveにvalidation機能を追加
        # saveを呼ぶとsave_with_validation、元のsaveはsave_without_validationで呼べるようになる
        alias_method_chain :save, :validation
        alias_method_chain :save!, :validation
      end
    end

    # alias_method_chainの設定と同時に以下の二つのメソッドも定義する必要がある

    def save_with_validation(perform_validation = true)
      if perform_validation && valid? || !perform_validation
        save_without_validation
      else
        false
      end
    end

    def save_with_validation!
      if valid?
        save_without_validation!
      else
        raise RecordInvalid.new(self)
      end
    end
  end
end

alias_method_chainが不要となる代替策1(シンプルなオーバーライド)

alias_method_chainは以下のような、シンプルなオブジェクト指向的な解決策で代用できる。

module Greetings
  def greet
    "hello"
  end
end

class MyClass
  include Greetings
end

MyClass.new.greet # => "hello"

greetメソッドに機能を追加する際にエイリアスを使うのではなく、別のモジュールにgreetメソッドを定義してオーバーライドし、そちらをインクルードすればalias_method_chainと同様の挙動がよりシンプルに実現できる。

module EnthusiasticGreetings
  include Greetings # 元のGreetingsを継承チェーンに追加

  def greet
    "Hey, #{super}!" # 元の実装をsuperで呼び出して機能を追加
  end
end

class MyClass
  include EnthusiasticGreetings
end

MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Greetings]
MyClass.new.greet # => "Hey, hello!"

現代のRailsのActiveRecord::Validationsも、この方法で実装されている。

alias_method_chainによるエイリアスの差し替えではなく、同名のsaveメソッドを定義してsuperで元の実装に委譲するシンプルな構造になっている。

# 現代のRailsのActiveRecord::Validationsより抜粋・簡略化
module ActiveRecord
  module Validations
    # 妥当性確認が成功したら通常のsaveのコードを実行し、失敗したらfalseを戻す
    def save(**options)
      perform_validations(options) ? super : false
    end

    def save!(**options)
      perform_validations(options) ? super : raise_validation_error
    end

    private

    def perform_validations(options = {})
      options[:validate] == false || valid?(options[:context])
    end
  end
end

alias_method_chainが不要となる代替策2(Module#prepend)

前項のシンプルなオーバーライドによるalias_method_chainの代替はModuleのメソッドにおいては有用だが、以下のようなクラスに直接定義されたメソッドにおいては、継承チェーンでクラスよりも祖先側にインクルードされてしまうのでオーバーライドすることができない。

module EnthusiasticGreetings
  def greet
    "Hey, #{super}!"
  end
end

class MyClass
  include EnthusiasticGreetings

  def greet
    "hello"
  end
end

# MyClassの上にincludeされている
MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Object]

# EnthusiasticGreetings#greetをMyClass#greetが上書きしてしまう
MyClass.new.greet # => "hello"

この解決策として、prependを使用しEnthusiasticGreetingsMyClassの継承チェーンの下に挿入することで通常のオーバーライドとsuperの呼び出しの技法が使えるようになる。

module EnthusiasticGreetings
  def greet
    "Hey, #{super}!"
  end
end

class MyClass
  prepend EnthusiasticGreetings # includeではなくprependを使用

  def greet
    "hello"
  end
end

# EnthusiasticGreetingsがMyClassより下に挿入される
MyClass.ancestors[0..2] # => [EnthusiasticGreetings, MyClass, Object]

# EnthusiasticGreetings#greetが呼ばれ、superでMyClass#greetに委譲される
MyClass.new.greet # => "Hey, hello!"

まとめ

  • alias_method_chainは冗長なエイリアス設定を自動化するRails独自のメソッドで、アラウンドエイリアスを簡潔に表現するために使われていた
  • モジュールで同名メソッドを定義しsuperで元実装に委譲すれば代用できるが、クラスに直接定義されたメソッドはincludeしたモジュールより継承チェーン上、下に来るためオーバーライドできない
  • Ruby 2.0で導入されたModule#prependは対象クラスの下にモジュールを挿入するため通常のオーバーライドが可能となり、alias_method_chainは役割を終えて廃止された

参考文献

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

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?