Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the variable scope within `class_eval` string?

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?

like image 346
steveyang Avatar asked Feb 27 '12 14:02

steveyang


People also ask

What is class_eval?

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.

What is eval in Ruby?

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.

What is Instance_eval?

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.


1 Answers

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
like image 63
Selman Ulug Avatar answered Oct 11 '22 04:10

Selman Ulug