0

Why is Ruby forcing me to instantiate/store these classes inside of local variables instead of instance variables?

Before I changed my code to make it functional, I had this:

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

which threw this error:

% 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

I decided to try instantiating the class (Convert) inside of each method and was successful:

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

Why wouldn't Ruby recognize the instance variable as an object in the first try?

random
  • 9,774
  • 10
  • 66
  • 83
boulder_ruby
  • 38,457
  • 9
  • 79
  • 100
  • Because that's not how you declare instance variables in Ruby. In a class declaration you're in the class, not an instance, so a @variable isn't what you believe it to be. – Dave Newton Jun 25 '12 at 21:44
  • 1
    So basically it didn't work because i DEFINED the instance OUTSIDE of a method. Had I done it inside of an initialize method it would've worked. Right? Umm....inside of a test this doesn't seem to be working...OK, I think I get it though. Thanks guys. – boulder_ruby Jun 25 '12 at 22:18
  • Which would be the most efficient code? Using a @@class_var declaration or doing it the way I've done above? – boulder_ruby Jun 25 '12 at 22:30

3 Answers3

3

Declaring @cv outside the tests makes it an instance variable for TestConverter - not an instance of TestConverter!

Probably the easiest way to get around that would be to make it a class variable: @@cv.

If you're still confused, consider this example:

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

puts Foo.instance_variables
puts Foo.new.instance_variables
Max
  • 21,123
  • 5
  • 49
  • 71
2

This is often confusing to people (including me) because it works differently than what you would expect from other languages. I'll try to illustrate with a Java example:

class Foo
  @bar = 42
end

Is NOT equivalent to

 public class Foo {
      private int bar = 42;
 }

But actually roughly equivalent to

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

Try it out in your IRB:

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

So why is this? In Ruby, everything is an object! So any class (e.g. Foo) is an instance of the class Class (sounds confusing, I know). Anything inside between class Foo; end is executed in the scope of the instance Foo of the class Class.

That is by no means a complete explanation, so you should really read up on the details.

fresskoma
  • 25,481
  • 10
  • 85
  • 128
0
assert_equal(0.0, @cv.@celsius(32))

Shouldn't this be

assert_equal(0.0, @cv.celsius(32))
Usman
  • 2,325
  • 1
  • 23
  • 18
  • Yes, if it was actually an instance variable... But it isn't an instance variable of what the OP believes it is. Ultimately this doesn't answer the real question, which is a misunderstanding of Ruby. – Dave Newton Jun 25 '12 at 21:48
  • @David "OP" means "Original Poster". It's a sex-agnostic and short way to refer to the person who is asking the question. – Phrogz Jun 25 '12 at 22:26