Ruby Instance_Eval on a Class with Attr_Accessor

Ruby instance_eval on a class with attr_accessor

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.

instance_eval doesn't work with att_accessor?

The culprit lies in this part of the code:

a.instance_eval do
b = 2
end

Although b = 2 is evaluated in the context of your instance, it doesn't call the setter. Instead it just creates a new local variable called b in the current scope. To call the setter, you have to further clarify your code to resolve the ambiguity:

a.instance_eval do
self.b = 2
end

Why can't I use attr_accessor inside initialize?

You can't call attr_accessor on the instance, because attr_accessor is not defined as an instance method of MyClass. It's only available on modules and classes. I suspect you want to call attr_accessor on the instance's metaclass, like this:

class MyClass
def initialize(varname)
class <<self
self
end.class_eval do
attr_accessor varname
end
end
end

o1 = MyClass.new(:foo)
o2 = MyClass.new(:bar)
o1.foo = "foo" # works
o2.bar = "bar" # works
o2.foo = "baz" # does not work

Ruby object initialization using instance_eval

Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.

You don’t need to experiment with such an overcomplicated example, the below won’t work as well:

class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end

only this will:

class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end

For your example, you must explicitly call the method on a receiver:

person = Person.new do
self.first_name = "Adam"
end

attr_accessor on singleton objects not working as expected

The typical usage for class << self is to make a block where instance methods get defined as class methods. This lets you skip typing the self. in the method definition and also makes other facilities available like private/protected.

How this works is by opening up Dog's singleton class and adding instance methods to it. Instance methods of Dog's singleton class become class methods on Dog. This is just part of the definition of singleton classes.

In the case of attr_accessor, that's a method you call in the class scope of Dog, and which defines instance methods on Dog.

When you call attr_accessor on Dog's singleton class, it creates instance methods on Dog's singleton class. Instance methods of Dog's singleton class become class methods on Dog. That's why you can use Dog.name = and not Dog.new.name = with your code.

Difference between @instance_variable and attr_accessor

An instance variable is not visible outside the object it is in; but when you create an attr_accessor, it creates an instance variable and also makes it visible (and editable) outside the object.

Example with instance variable (not attr_accessor)

class MyClass
def initialize
@greeting = "hello"
end
end

m = MyClass.new
m.greeting #results in the following error:
#NoMethodError: undefined method `greeting' for #<MyClass:0x007f9e5109c058 @greeting="hello">

Example using attr_accessor:

class MyClass
attr_accessor :greeting

def initialize
@greeting = "hello"
end
end

m2 = MyClass.new
m2.greeting = "bonjour" # <-- set the @greeting variable from outside the object
m2.greeting #=> "bonjour" <-- didn't blow up as attr_accessor makes the variable accessible from outside the object

Hope that makes it clear.

Difference between class_eval and instance_eval in a module

In your example there is no difference.

> Foo1.instance_eval { puts self } 
Foo1

> Foo1.class_eval { puts self }
Foo1

The instance of a class is the class itself. instance_eval gives you nothing "extra" here. But if you use it on an instance of a class, then you get different behaviour. I.e. Foo1.new.instance_eval ....

See here for a good explanation:

How to understand the difference between class_eval() and instance_eval()?



Related Topics



Leave a reply



Submit