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?
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
来源:https://stackoverflow.com/questions/9466624/whats-the-variable-scope-within-class-eval-string