instance_eval 與 class_eval 差異

instance_eval 與 class_eval 從英文語意上來看感覺很簡單,一個是 instance level,另個是 class level,實際瞭解後才發現沒這麼單純,instance 也非平常所理解的 instance。

先看看以下的範例:

String.instance_eval do
  def from_instance_eval
    self
  end
end

String.class_eval do
  def from_class_eval
    self
  end
end

p String.from_instance_eval      #String
p "string".from_class_eval       #"string"

begin
  String.from_class_eval
rescue Exception  => e
  p e                            # => #<NoMethodError: undefined method `from_class_eval' for String:Class>
end

begin
  "string".from_instance_eval
rescue Exception  => e
  p e                            # => #<NoMethodError: undefined method `from_instance_eval' for "string":String>
end

from gist

看起來好像是 class method 要用 instance_eval 來定義,instance method 要用 class_eval 來做?再讓我們深入探討一下~

instance_eval

"string2".instance_eval { p self }   # => "string2"
begin
  "string2".class_eval { p self }
rescue Exception  => e
  p e                               # => #<NoMethodError: undefined method `class_eval' for "string2":String>
end

from gist

到此我們可以發現,instance_eval 用來定義於任何 object 上,即使是 class。(這邊要回想到與切記,在 ruby 的世界裡,任何東西也包含 Class,都是物件 object)

class_eval

而 class_eval 也很好理解,以下兩個東西是相等的:

  1. class_eval 寫法
class Thing
end
Thing.class_eval { def far; end }
  1. 原始 class 定義寫法
class Thing
  def far
  end
end

小記

實務上,這可以拿來用作擴充 rails gem 所定義的 class。例如 globalize 這個 gem,會針對 model 多開一個 translation 的 class。

像是在 Post 使用 Globalize

class Post < ActiveRecord::Base
  translates :title, :text
end

那 Globalize 的 gem 就會幫我們多定義一個 class 用作多語系版本的用途,為「Post::Translation」
如果想要在這個 class 中多增加方法呢?在 config/initializers 內多一個檔案定義 module_eval 即可

Post::Translation.module_eval do
  def test
    puts "hello world"
  end
end

module_eval 與 class_eval 沒有太大差異,只是定義的 scope 不同而已。