Какова область видимости переменной в строке `class_eval`? - PullRequest
16 голосов
/ 27 февраля 2012

Я использую class_eval для написания кода, который будет выполняться в контексте текущего класса. В следующем коде я хочу добавить счетчик для изменения значений атрибутов.

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

Мое понимание class_eval состоит в том, что он оценивает блок в контексте класса времени выполнения - в моем случае, под class Foo. Я ожидаю, что приведенный выше код работает аналогично :

class Foo
  attr_count :bar
  @count = 0
  def bar= (attr_name)
    @attr_name = attr_name
    @count += 1
  end

  def bar
    @attr_name
  end
end

Однако приведенный выше код привел к ошибке, говоря, что ошибка вызвана @count += 1. Я не могу понять, почему @count имеет nil:NilClass в качестве супер?

(eval):5:in `bar=': undefined method `+' for nil:NilClass (NoMethodError)

С другой стороны, @selman дал решение поместить присваивание @count в метод экземпляра, и оно работает.

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

Почему меняется область действия переменной? Как class_eval выполняет следующую строку?

1 Ответ

12 голосов
/ 27 февраля 2012

это не о class_eval, а о @count. если вы определите эту переменную на уровне класса, это будет class instance variable, а не 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
...