What's the variable scope within `class_eval`

2020-05-19 00:51发布

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?

1条回答
放荡不羁爱自由
2楼-- · 2020-05-19 01:19

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
查看更多
登录 后发表回答