Ruby instance_eval on a class with attr_accessor

这一生的挚爱 提交于 2019-12-01 04:29:11

问题


I understand the basic difference between instance_eval and class_eval. What I've discovered though when playing around is something strange involving attr_accessor. Here's an example:

A = Class.new
A.class_eval{ attr_accessor :x }

a = A.new
a.x = "x"
a.x
=> "x"  # ... expected

A.instance_eval{ attr_accessor :y }

A.y = "y"
=> NoMethodError: undefined method `y=' for A:Class

a.y = "y"
=> "y"      # WHATTT?

How is it that:

  1. the instance_eval didn't at the accessor onto our A class (object)
  2. it then in fact added it onto instances of A?

回答1:


At first, your understanding (or intuition) is correct, methods defined inside #instance_eval and #class_eval are not the same

A = Class.new

A.instance_eval { def defined_in_instance_eval; :instance_eval; end }
A.class_eval { def defined_in_class_eval; :class_eval; end }

A.new.defined_in_class_eval # => :class_eval
A.defined_in_instance_eval # => :instance_eval

a side note: while self is the same in both instance_eval and class_eval, the default definee is different, see http://yugui.jp/articles/846

What really does the trick is Module#attr_accessor itself, look at its definition: http://rxr.whitequark.org/mri/source/vm_method.c#620

it does not use def, it does not read context, self or a default definee. It just "manually" inserts methods into a module. That's why the result is counterintuitive.




回答2:


For the difference between class_eval and instance_eval, see Dynamically creating class method

class A; end
A.class_eval do
    attr_accessor :x
    def barx; end
    define_method :foox do; end
end

print 'A.instance_methods  : '; p A.instance_methods(false).sort
print 'A.singleton_methods : '; p A.singleton_methods

class B; end
B.instance_eval do
    attr_accessor :y
    def bary; end
    define_method :fooy do; end
end

print 'B.instance_methods  : '; p B.instance_methods(false).sort
print 'B.singleton_methods : '; p B.singleton_methods

class C; end
singleton_class = class << C; self end
singleton_class.instance_eval do
    attr_accessor :z
    def barz; puts 'where is barz ?' end
    define_method :fooz do; end
end

print 'C.instance_methods  : '; p C.instance_methods(false).sort
print 'C.singleton_methods : '; p C.singleton_methods

print 'singleton_class.barz : '; singleton_class.barz
print 'singleton_class.methods  : '; p singleton_class.methods(false)

Output (ruby 1.8.6):

A.instance_methods  : ["barx", "foox", "x", "x="]
A.singleton_methods : []
B.instance_methods  : ["fooy", "y", "y="]
B.singleton_methods : ["bary"]
C.instance_methods  : []
C.singleton_methods : ["z", "z=", "fooz"]
singleton_class.barz : where is barz ?
singleton_class.methods  : ["barz"]

As you can see with B, despite the fact that instance_eval usually creates singleton methods, obviously attr_accessor and define_method force the definition of instance methods.




回答3:


A.singleton_class.class_eval { attr_accessor :y }
A.y = 'y'
A.y



回答4:


The method attr_accessor is a class method such that, when called in the body of a class, then accessor methods are defined on the instances of that class.

When you do A.class_eval{...}, you are calling it within the body of a class A, so its instances such as a are assigned accessors.

When you do A.instance_eval{...}, you calling it within a non-body of a class A, so its instances are not assigned accessors.

If you do Class.class_eval{attr_accessor :z}, then you are calling it withing the body of a class Class, so its instances such as A will be assigned accessors: A.z = ....



来源:https://stackoverflow.com/questions/14428531/ruby-instance-eval-on-a-class-with-attr-accessor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!