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
Ruby on Rails: Radio Buttons for Collection Select
Rotate Bits Right Operation in Ruby
What's the Best Way to Return an Enumerator::Lazy When Your Class Doesn't Define #Each
Ssl_Connect Error When Accessing Shopify API with Rubygem
How to Create a Custom Method for the Rails Console
Activerecord::Associationtypemismatch in Controller#Create on Dropdown Select for a Rails Self Join
Can't Install Ruby via Rvm, Error Running '_Rvm_Make -J4' on Ubuntu 22.04
Nested Form_For Singular Resource
Requiring a Ruby Gem in Ruby Script Breaks Cron Job Execution
How to Convert PDF to Excel or CSV in Rails 4
Get Pry to Display Characters Like [äöüßÄÖÜß] (Utf-8 Encoding)? (Possibly Windows-Specific Issue)
Best Way to Find_Or_Create_By_Id But Update the Attributes If the Record Is Found
Handle Check Box Forms with an ':Has_Many :Through' Record Association
Net-Ssh and Remote Environment
Accessing the Child Instance in a Rabl Template
What's the Difference Between the Ruby Irb Prompt Modes