モジュールの基礎

Railsプラグインを作る上で、Rubyの基本的なところがやっぱり重要なので、その辺もまとめておきます。

Module#included

module Hoge
  def foo_with_hoge
    foo_without_hoge + "with HOGE"
  end
end

というモジュールがあって、

class Foo
  include Hoge
  def foo
    "foo"
  end
  alias_method_chain :foo, :hoge  
end

という風に拡張させることができますが、alias_method_chainの行を書くために、これではFooを実装する人がHogeの中身をある程度知っている必要が生じてしまいます。そういうことはできるだけ少ない方が楽ちんなので、こんな風に書き換えます。

module Hoge
  def self.included(klass)
    klass.module_eval do
      alias_method_chain :foo, :hoge
    end
  end

  def foo_with_hoge
    foo_without_hoge + "with HOGE"
  end
end

という風にincludedという特異メソッドを定義してあげると

class Foo
  include Hoge
  def foo
    "foo"
  end
end

Fooの方はinclude Hogeと書くだけでfooメソッドを拡張することができます。

module_evalとinstance_eval

module_evalはModuleのメソッド、instance_evalはObjectのメソッドです。

instance_eval

対象のオブジェクトに対して直接操作するものです。

Time.instance_eval do
  def now_utc
    self.now.utc
  end
end
Time.now_utc
# => Thu Feb 18 16:17:32 UTC 2010

このnow_utcメソッド内のselfはTimeクラス自身を指します。

module_eval

モジュール(クラスも含む)に対して、そのインスタンス向けのメソッドなどを定義したりする操作を行います。

Time.module_eval do
  def utc_strftime(*args)
    self.utc.strftime(*args)
  end
end
Time.new.strftime('%c')
# => "Fri Feb 19 01:25:58 2010"
Time.new.utc_strftime('%c')
# => "Thu Feb 18 16:25:48 2010"

要は、普通にclassやmoduleで定義する時と同じで、このutc_strftime内のselfはTime.newで生成されたTimeのインスタンスです。

クラスはモジュール

http://www.ruby-lang.org/ja/man/html/Class.html にもある通り、ClassのスーパークラスはModuleです。

だからTimeでmodule_evalが使えるのです。Time自身はClassのインスタンスなので、Classの継承元であるModuleで用意されているmodule_evalが使えます。

モジュールに対してmodule_evalは不自然だからか、実はclass_evalというメソッドもありますが、これはModuleのメソッドで、module_evalの別名です。

クラスもObject

ClassはModuleを継承したものですが、Moduleの継承元はObjectです。なので、もちろんクラスやモジュールでもObjectのメソッドが使用できます。
なので、Timeに対してObjectのメソッドであるinstance_evalが使える訳です。

特異メソッド

Javaなどの静的な型付け言語から入るとメソッドの定義はクラス(あるいはそれに似たもの)に対して行うものと思ってしまいがちですが、Rubyではメソッドはクラスやモジュールを使わなくても定義できます。

obj = Object.new
def obj.foo
  "foo"
end
obj.foo
# => "foo"

このobj専用のメソッドであるfooを普通に定義したメソッドと区別して「特異メソッド」と呼びます。
これをinstance_evalを使って書くとこんな感じ。

obj = Object.new
obj.instance_eval do
  def foo
    "foo"
  end
end
obj.foo
# => "foo"

例えば、このobjをTimeに置き換えてみます。

Time.instance_eval do
  def foo
    "foo"
  end
end
Time.foo
# => "foo"

instance_evalを使わずに書くと

class Time
  def self.foo
    "foo"
  end
end
Time.foo
# => "foo"

これは見たことありますよね?Timeのクラスメソッドを定義しています。
つまりクラスメソッドはTimeというオブジェクトの特異メソッドなわけです。

ちなみにこの特異メソッドを定義するための特殊な書き方がこれ。

class Time
  class << self
    def foo
      "foo"
    end
  end
end
Time.foo
# => "foo"

"class <<"という書き方ですが、別にクラスにしか使えない訳ではありません。

obj = Object.new
class << obj
  def foo
    "foo"
  end
end
obj.foo
# => "foo"

特異メソッドのエイリアス

上の方の話で、インスタンスメソッドに対してalias_methodを使って別名を付けることをしていましたが、特異メソッドのエイリアスについてはModule#alias_methodではできません。

obj = Object.new
class << obj
  def foo
    "foo"
  end
end
obj.foo
# => "foo"

という定義があったときに、fooを拡張したい場合

obj.instance_eval do
  def foo_with_hoge
    foo_without_hoge + " with HOGE"
  end
  
  alias :foo_without_hoge :foo
  alias :foo :foo_with_hoge
end
obj.foo
# => "foo with HOGE"

という風に、aliasを使うことで実現できます。
alias_methodはModuleのインスタンスメソッドでしたが、aliasはメソッドではありません。メソッドに関する別名を付ける記述方法なので、メソッドと違いカンマで区切らないので要注意です。
http://www.ruby-lang.org/ja/man/html/_A5AFA5E9A5B9A1BFA5E1A5BDA5C3A5C9A4CEC4EAB5C1.html#alias

Object#extend

http://www.ruby-lang.org/ja/man/html/Object.html#extend
特異メソッドをまとめてモジュールにして、それをextendすることで特異メソッドを定義することができます。

obj = Object.new
class << obj
  def foo
    "foo"
  end
end
obj.foo
# => "foo"

があった場合に

module SingletonMethods
  def self.extended(obj)
    obj.instance_eval do
      alias :foo_without_hoge :foo
      alias :foo :foo_with_hoge
    end
  end
  
  def foo_with_hoge
    foo_without_hoge + " with HOGE"
  end
end
obj.extend(SingletonMethods)
obj.foo
# => "foo with HOGE"

という風にextendedを定義すると、前述のインスタンスのように特異メソッドも拡張することができます。

よくあるパターン

まとめになりますが、例えばこんなクラスがあった場合

class A
  def self.foo
    "foo"
  end
  
  def bar
    "bar"
  end
end
A.foo
# => "foo"
A.new.bar
# => "bar"

これを拡張するモジュールはこんな感じで書くことがよくあります。

module Hoge
  def self.included(mod)
    mod.extend(ClassMethods)
    # ClassMethods.extendedを用意してそこに書いても良いんですけど、面倒なのでここにまとめちゃいます。
    mod.instance_eval do
      alias :foo_without_hoge :foo
      alias :foo :foo_with_hoge
    end
    mod.module_eval do
      alias_method :bar_without_hoge, :bar
      alias_method :bar, :bar_with_hoge
    end
  end

  module ClassMethods
    def foo_with_hoge
      foo_without_hoge + " with HOGE"
    end
  end

  def bar_with_hoge
    bar_without_hoge + " with HOGE"
  end
end

という拡張を用意して、

A.module_eval do
  include Hoge
end

で、クラスに拡張を適用すると

A.foo
# => "foo with HOGE"
A.new.bar
# => "bar with HOGE"

という風に動いちゃうわけです。

ここのalias_methodを使っている部分は、Railsなどのactive_supportを使っている環境だったらalias_method_chainを使うといいですね。

まとめ

alias_methodとaliasをうまく使うと既存のクラスのメソッドを拡張できて、それらの拡張もモジュールを使うと分かりやすくまとめられます。
この辺を頭に入れた上でいろんなプラグインRails本体のコードを読むとより理解が深まると思います。
で、ガリガリプラグインを書きましょう。

変なところがあったらご指摘くださいませ。