I am using class_eval
to write code to be executed under the context of current class. In the following code, I want to add a counter for changes of attribute values.
class Class
def attr_count(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name # create the attribute's getter
class_eval %Q{
@count = 0
def #{attr_name}= (attr_name)
@attr_name = attr_name
@count += 1
end
def #{attr_name}
@attr_name
end
}
end
end
class Foo
attr_count :bar
end
f = Foo.new
f.bar = 1
My understanding of class_eval
is that it evaluates the block in the context of the runtime class - in my case, under class Foo
. I expect the above code runs similar as:
class Foo
attr_count :bar
@count = 0
def bar= (attr_name)
@attr_name = attr_name
@count += 1
end
def bar
@attr_name
end
end
However the above code resulted in error saying, the error is caused by @count += 1
. I cannot figure out why @count
has nil:NilClass
as its super?
(eval):5:in `bar=': undefined method `+' for nil:NilClass (NoMethodError)
On the other hand, @selman has given a solution to put @count
assignment within the instance method and it works.
class Class
def attr_count(attr_name)
#...
class_eval %Q{
def #{attr_name}= (attr_name)
@attr_name = attr_name
if @count
@count += 1
else
@count = 1
end
end
#...
}
end
end
Why changes the variable scope works? How does class_eval
execute its following string?
class_eval is a method of the Module class, meaning that the receiver will be a module or a class. The block you pass to class_eval is evaluated in the context of that class. Defining a method with the standard def keyword within a class defines an instance method, and that's exactly what happens here.
eval. The eval method of Kernel allows you to evaluate a string in the current context. The eval method also allows you to optionally specify a binding. If a binding is given, the evaluation will be performed in the context of the binding.
The instance_eval method defines a method for one object only, whereas the class_eval method defines it for ALL objects or instances of a class.
it is not about class_eval
it is about @count
. if you define this variable at class level it will be a class instance variable
not an instance variable
.
class Class
def attr_count(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name # create the attribute's getter
class_eval %Q{
def #{attr_name}= (attr_name)
@attr_name = attr_name
if @count
@count += 1
else
@count = 1
end
end
def #{attr_name}
@attr_name
end
}
end
end
class Foo
attr_count :bar
end
f = Foo.new
f.bar = 1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With