Ruby хранит классы внутри локальных переменных вместо переменных экземпляра

Почему Ruby заставляет меня создавать/сохранять эти классы внутри локальных переменных вместо переменных экземпляра?

Прежде чем я изменил свой код, чтобы сделать его функциональным, у меня было следующее:

require 'test/unit'
require 'converter'

class TestConverter < Test::Unit::TestCase

  @cv = Convert.new

  def test_celsius
    assert_equal(100.0, @cv.celsius(212))
    assert_equal(0.0, @cv.@celsius(32))
  end

  def test_fahrenheit
    assert_equal(212.0, @cv.fahrenheit(100))
    assert_equal(32.0, @cv.fahrenheit(0))
  end

end

который выдал эту ошибку:

% ruby -I. converter_test.rb                                                  ✭
Run options: 

# Running tests:

EE

Finished tests in 0.000616s, 3246.7638 tests/s, 0.0000 assertions/s.

  1) Error:
test_celsius(TestConverter):
NoMethodError: undefined method `celsius' for nil:NilClass
    converter_test.rb:9:in `test_celsius'

  2) Error:
test_fahrenheit(TestConverter):
NoMethodError: undefined method `fahrenheit' for nil:NilClass
    converter_test.rb:14:in `test_fahrenheit'

2 tests, 0 assertions, 0 failures, 2 errors, 0 skips

Я решил попробовать создать экземпляр класса (Convert) внутри каждого метода и преуспел:

require 'test/unit'
require 'converter'

class TestConverter < Test::Unit::TestCase

  #@cv = Convert.new
  #instantiated the class in each method instead of here

  def test_celsius
    cv = Convert.new
    assert_equal(100.0, cv.celsius(212))
    assert_equal(0, cv.celsius(32))
  end

  def test_fahrenheit
    cv = Convert.new
    assert_equal(212, cv.fahrenheit(100))
assert_equal(32, cv.fahrenheit(0))
end
end
ddouglas@coders:~/Develop/davincicoders$ ruby -I. converter_test.rb
Run options: 

# Running tests:

..

Finished tests in 0.001894s, 1055.9149 tests/s, 2111.8298 assertions/s.

2 tests, 4 assertions, 0 failures, 0 errors, 0 skips

Почему бы Ruby не распознать переменную экземпляра как объект с первой попытки?


person boulder_ruby    schedule 25.06.2012    source источник
comment
Потому что это не то, как вы объявляете переменные экземпляра в Ruby. В объявлении класса вы находитесь в классе, а не в экземпляре, поэтому @переменная — это не то, чем вы ее считаете.   -  person Dave Newton    schedule 26.06.2012
comment
Так что в основном это не сработало, потому что я ОПРЕДЕЛИЛ экземпляр ВНЕШНЕГО метода. Если бы я сделал это внутри метода инициализации, это сработало бы. Верно? Хм... внутри теста это, похоже, не работает... Хорошо, я думаю, что понял. Спасибо, парни.   -  person boulder_ruby    schedule 26.06.2012
comment
Какой код будет наиболее эффективным? Используя объявление @@class_var или делая это так, как я сделал выше?   -  person boulder_ruby    schedule 26.06.2012


Ответы (3)


Объявление @cv вне тестов делает его переменной экземпляра для TestConverter, а не экземпляром TestConverter!

Вероятно, самый простой способ обойти это — сделать его переменной класса: @@cv.

Если вы все еще запутались, рассмотрите этот пример:

class Foo
  @x = 3
  def initialize
    @y = 4
  end
end

puts Foo.instance_variables
puts Foo.new.instance_variables
person Max    schedule 25.06.2012

Это часто сбивает с толку людей (включая меня), потому что работает не так, как вы ожидаете от других языков. Я попытаюсь проиллюстрировать это на примере Java:

class Foo
  @bar = 42
end

НЕ эквивалентен

 public class Foo {
      private int bar = 42;
 }

Но на самом деле примерно эквивалентно

 public class Foo {
      private static int bar = 42;
 }

Попробуйте это в своем IRB:

class Foo
  attr_accessor :bar
  @bar = 42
  class << self
    attr_accessor :bar
  end
end
Foo.bar # => 42
Foo.new.bar # => nil

Так почему же? В Ruby все является объектом! Таким образом, любой класс (например, Foo) является экземпляром класса Class (знаю, звучит запутанно). Все, что находится между class Foo; end, выполняется в рамках экземпляра Foo класса Class.

Это ни в коем случае не полное объяснение, так что вы должны действительно прочитать подробности.

person fresskoma    schedule 25.06.2012

assert_equal(0.0, @cv.@celsius(32))

Разве это не должно быть

assert_equal(0.0, @cv.celsius(32))
person Usman    schedule 25.06.2012
comment
Да, если бы это была переменная экземпляра... Но это не переменная экземпляра того, что считает ОП. В конечном счете, это не отвечает на настоящий вопрос, что является неправильным пониманием Ruby. - person Dave Newton; 26.06.2012
comment
@David OP означает оригинальный постер. Это независимый от секса и короткий способ обратиться к человеку, который задает вопрос. - person Phrogz; 26.06.2012