2012-12-13 172 views
3

我在寫一個類方法來創建另一個類方法。在類方法的上下文中,class_evalinstance_eval的工作方式似乎有點奇怪。爲了說明:動態創建類方法

class Test1 
    def self.add_foo 
    self.class_eval do # does what it says on the tin 
     define_method :foo do 
     puts "bar" 
     end 
    end 
    end 
end 

Test1.add_foo # creates new instance method, like I'd expect 
Test1.new.foo # => "bar" 


class Test2 
    def self.add_foo 
    self.instance_eval do # seems to do the same as "class_eval" 
     define_method :foo do 
     puts "bar" 
     end 
    end 
    end 
end 

Test2.add_foo # what is happening here?! 
Test2.foo # => NoMethodError 
Test2.new.foo # => "bar" 


class Test3 
    def self.add_foo 
    (class << self; self; end).instance_eval do # call explicitly on metaclass 
     define_method :foo do 
     puts "bar" 
     end 
    end 
    end 
end 

Test3.add_foo # => creates new class method, as I'd expect 
Test3.foo # => "bar" 

我的理解是,類方法是在類的有問題的元類(Test2在這種情況下)定義的實例方法。基於這個邏輯,我期望類方法調用add_foo的接收器是元類。

  • 什麼是self是指Test2.add_foo裏面的方法?
  • 爲什麼在此接收器對象上調用instance_eval創建實例方法?
+0

instance_eval在這裏解釋:http://stackoverflow.com/questions/13775794/a-confusing-case-in-ruby-metaprogramming/13790992#13790992 – BernardK

+0

有趣的:http://stackoverflow.com/questions/14428531/ ruby-instance-eval-on-a-class-with-attr-accessor/14431718#14431718和鏈接的文章。 – BernardK

回答

5

instance_evalclass_eval之間的主要區別在於一個實例的上下文中instance_eval作品,而類的上下文內class_eval作品。我不知道你是多麼熟悉Rails的,但讓我們來看看本作的例子:

class Test3 < ActiveRecord::Base 

end 

t = Test3.first 
t.class_eval { belongs_to :test_25 } #=> Defines a relationship to test_25 for this instance 
t.test_25 #=> Method is defined (but fails because of how belongs_to works) 

t2 = Test3.find(2) 
t2.test_25 #=> NoMethodError 

t.class.class_eval { belongs_to :another_test } 
t.another_test #=> returns an instance of another_test (assuming relationship exists) 
t2.another_test #=> same as t.another_test 

t.class_eval { id } #=> NameError 
t.instance_eval { id } #=> returns the id of the instance 
t.instance_eval { belongs_to :your_mom } #=> NoMethodError 

這是因爲belongs_to實際上是一個方法調用類體,它不能調用的範圍內發生的是從一個實例。當您嘗試使用class_eval調用id時,它會失敗,因爲id是在實例上定義的方法,而不是在類中定義的方法。

定義使用class_evalinstance_eval的方法在針對實例調用時基本相同。他們只會在被調用的對象實例上定義一個方法。

t.class_eval do 
    def some_method 
    puts "Hi!" 
    end 
end 

t.instance_eval do 
    def another_method 
    puts "Hello!" 
    end 
end 

t.some_method #=> "Hi!" 
t.another_method #=> "Hello!" 

t2.some_method #=> NoMethodError 
t2.another_method #=> NoMethodError 

但是,它們在處理班級時有所不同。

t.class.class_eval do 
    def meow 
    puts "meow!" 
    end 
end 

t.class.instance_eval do 
    def bark 
    puts "woof!" 
    end 
end 

t.meow #=> meow! 
t2.meow #=> meow! 

t.bark #=> NoMethodError 
t2.bark #=> NoMethodError 

那麼樹皮去哪了?它定義在類的單例類的實例上。我會在下面解釋更多。但現在:

t.class.bark #=> woof! 
Test3.bark #=> woof! 

所以回答你什麼self是指類體中的問題,你可以構造一個小測試:

a = class Test4 
    def bar 
    puts "Now, I'm a #{self.inspect}" 
    end 

    def self.baz 
    puts "I'm a #{self.inspect}" 
    end 

    class << self 
    def foo 
     puts "I'm a #{self.inspect}" 
    end 

    def self.huh? 
     puts "Hmmm? indeed" 
    end 

    instance_eval do 
     define_method :razors do 
     puts "Sounds painful" 
     end 
    end 

    "But check this out, I'm a #{self.inspect}" 
    end 
end 

puts Test4.foo #=> "I'm a Test4" 
puts Test4.baz #=> "I'm a Test4" 
puts Test4.new.bar #=> Now I'm a #<Test4:0x007fa473358cd8> 
puts a #=> But check this out, I'm a #<Class:Test4> 

那麼,什麼是這裏發生的事情是,在首先在上面聲明,我們看到檢查告訴我們,在類方法體的上下文中的self指的是類Test4。在第二個puts中,我們看到了相同的事物,它的定義有所不同(使用self.method_name表示法來定義類方法)。第三,我們看到self引用了Test4的一個實例。最後一個有點有趣,因爲我們看到的是self指的是Class的一個實例,名爲Test4。這是因爲當你定義一個類時,你正在創建一個對象。 Ruby中的所有東西都是一個對象。這個對象的實例被稱爲元類或特徵類或單例類。

您可以使用class << self慣用語訪問本徵類。當你在那裏時,你實際上可以訪問特徵類的內部。您可以在本徵類內定義實例方法,這與調用self.method_name一致。但是由於您處於特徵類的範圍內,因此您可以將方法附加到特徵類的特徵類。

Test4.huh? #=> NoMethodError 
Test4.singleton_class.huh? #=> Hmmm? indeed 

當你在方法的上下文中調用instance_eval,你真的類本身,這意味着你正在創建TEST4實例方法調用instance_eval。那麼我在本徵類內部稱爲instance_eval的地方呢?它在Test4的本徵類的實例上創建一個方法:

Test4.razors #=> Sounds painful 

希望這可以清除您的一些問題。我知道我已經學會了一些輸入這個答案的東西!

+0

答案可以改進。我將展示instance_eval和class_eval在類上執行時如何執行相同的操作,以及如何定義eigenclass上的方法是不同的。 –

+0

感謝您的迴應!我同意@SergioTulentsev--很高興在這裏進一步澄清一下。最重要的是''self'在'Test2'中引用了什麼? – motns

+1

您可能想要解釋上述第一個和第二個放置示例之間的方法解析方式是如何不同的(一個解析到類,另一個解決方案解析爲沒有在該類上找到該方法後的本徵類)。 –