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 也很好理解,以下兩個東西是相等的:
- class_eval 寫法
class Thing
end
Thing.class_eval { def far; end }
- 原始 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 不同而已。