mot*_*tns 3 ruby metaprogramming
我正在编写一个类方法来创建另一个类方法.似乎是围绕着如何一些陌生感class_eval和instance_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"
Run Code Online (Sandbox Code Playgroud)
我的理解是类方法是在所讨论的类的元类上定义的实例方法(Test2在本例中).基于该逻辑,我希望类方法调用的接收器add_foo是元类.
self内部是指什么Test2.add_foo?instance_eval此接收器对象会创建实例方法?之间的主要区别instance_eval和class_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
Run Code Online (Sandbox Code Playgroud)
发生这种情况是因为belongs_to实际上是在类主体的上下文中发生的方法调用,您无法从实例调用它.当您尝试调用id时class_eval,它会失败,因为它id是在实例上定义的方法,而不是在类中定义的方法.
在针对实例调用时,使用两者定义方法class_eval并且instance_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
Run Code Online (Sandbox Code Playgroud)
然而,他们在与班级打交道时有所不同.
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
Run Code Online (Sandbox Code Playgroud)
那么树皮在哪里?它在类'singleton类的实例上定义.我将在下面解释.但现在:
t.class.bark #=> woof!
Test3.bark #=> woof!
Run Code Online (Sandbox Code Playgroud)
因此,要回答关于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>
Run Code Online (Sandbox Code Playgroud)
所以这里发生的事情是,在上面的第一个put语句中,我们看到inspect告诉我们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
Run Code Online (Sandbox Code Playgroud)
当您instance_eval在方法的上下文中调用时,您实际上是在调用instance_eval类本身,这意味着您正在Test4上创建实例方法.那我在eigenclass里面调用instance_eval怎么样?它在Test4的本征类实例上创建一个方法:
Test4.razors #=> Sounds painful
Run Code Online (Sandbox Code Playgroud)
希望这能解决您的一些问题.我知道我已经学会了一些输入这个答案的东西!