3

I'm trying to solve a temperature problem in Ruby at TestFirst.org. My goal is to write code to work with test as shown below. Here is the test spec:

require "10_temperature_object"

describe Temperature do
  describe "can be constructed with an options hash" do
    describe "in degrees fahrenheit" do
      it "at 50 degrees" do
        Temperature.new(:f => 50).in_fahrenheit.should == 50
      end
      describe "and correctly convert to celsius" do
        it "at freezing" do
          Temperature.new(:f => 32).in_celsius.should == 0
        end
        it "at boiling" do
          Temperature.new(:f => 212).in_celsius.should == 100
        end
        it "at body temperature" do
          Temperature.new(:f => 98.6).in_celsius.should == 37
        end
        it "at an arbitrary temperature" do
          Temperature.new(:f => 68).in_celsius.should == 20
        end
      end
    end
    describe "in degrees celsius" do
      it "at 50 degrees" do
        Temperature.new(:c => 50).in_celsius.should == 50
      end
      describe "and correctly convert to fahrenheit" do
        it "at freezing" do
          Temperature.new(:c => 0).in_fahrenheit.should == 32
        end
        it "at boiling" do
          Temperature.new(:c => 100).in_fahrenheit.should == 212
        end
        it "at body temperature" do
          Temperature.new(:c => 37).in_fahrenheit.should be_within(0.1).of(98.6)
          # Why do we need to use be_within here?
          # See http://www.ruby-forum.com/topic/169330
          # and http://groups.google.com/group/rspec/browse_thread/thread/f3ebbe3c313202bb
          # Also, try "puts 0.5 - 0.4 - 0.1" -- pretty crazy, right?
        end
      end
    end
  end

  # Factory Method is a design pattern, not a Ruby language feature.
  # One way to implement this pattern in Ruby is via class methods --
  # that is, methods defined on the class (Temperature) rather than
  # on individual instances of the class.
  describe "can be constructed via factory methods" do
    it "in degrees celsius" do
      Temperature.from_celsius(50).in_celsius.should == 50
      Temperature.from_celsius(50).in_fahrenheit.should == 122
    end
    it "in degrees fahrenheit" do
      Temperature.from_fahrenheit(50).in_fahrenheit.should == 50
      Temperature.from_fahrenheit(50).in_celsius.should == 10
    end
  end

  # test-driving bonus:
  #
  # 1. make two class methods -- ftoc and ctof
  # 2. refactor to call those methods from the rest of the object
  #
  # run *all* the tests during your refactoring, to make sure you did it right
  #
  describe "utility class methods" do

  end

  # Here's another way to solve the problem!
  describe "Temperature subclasses" do
    describe "Celsius subclass" do
      it "is constructed in degrees celsius" do
        Celsius.new(50).in_celsius.should == 50
        Celsius.new(50).in_fahrenheit.should == 122
      end
      it "is a Temperature subclass" do
        Celsius.new(0).should be_a(Temperature)
      end
    end
    describe "Fahrenheit subclass" do
      it "is constructed in degrees fahrenheit" do
        Fahrenheit.new(50).in_fahrenheit.should == 50
        Fahrenheit.new(50).in_celsius.should == 10
      end
      it "is a Temperature subclass" do
        Fahrenheit.new(0).should be_a(Temperature)
      end
    end
  end
end

Here is my code:

class Temperature
    #initialize of temperature class
    def initialize(hash = {})   #option hash parameter
        @hash = hash    #accepts either :f or :c
    end
    def self.from_celsius(temp)
        @temp1 = temp
    end
    def self.from_fahrenheit(temp)
        @temp2 = temp
    end
    def in_fahrenheit
        if @hash.has_key?(:f)
            return @hash[:f]    #return value of @hash[:f]
        elsif @hash.has_key?(:c)
            return @hash[:c]*9.to_f/5+32    #formula to convert from C to F
        elsif @temp1.is_a? Numeric
            return @temp1*9.to_f/5+32
        else
            return @temp1
        end
    end
    def in_celsius
        if @hash.has_key?(:c)
            return @hash[:c]    #return value of @hash[:c]
        elsif @hash.has_key?(:f)
            return (@hash[:f]-32)*5.to_f/9  #formula to convert from F to C
        elsif @temp2.is_a? Numeric
            return (@temp2-32)*5.to_f/9
        else
            return @temp1
        end
    end
end

I passed the tests up to where it asks me to use the Factory Method to define the class methods from_celsius and from_fahrenheit, which gives this error: "NoMethodERror: undefined method "in_celsius""

Under from_celsius and from_fahrenheit, I was thinking of creating variables @temp1 and @temp2 and then use this to calculate the temperature based on in_fahrenheit and in_celsius but I needed to determine if the variables are not a symbol. That did not work either.

sawa
  • 165,429
  • 45
  • 277
  • 381
Guy
  • 79
  • 2
  • 12

1 Answers1

3

The problem is from_celsius and from_fahrenheit methods returns a Fixnum (since you passed a number as parameter in your tests). Fixnum doesn't have these methods.

Lets take this example:

Temperature.from_celsius(50).in_celsius.should == 50

Temperature.from_celsius(50) will return the value 50. Then ruby will try to call in_celsius for the 50 object.

50.in_celsius.should == 50

And it will fail. Also, you are defining instance variables (@temp1 and @temp2) inside class methods and this won't work. You could use these class methods to create an instance of Temperature perhaps?

def self.from_celsius(temp)
    Temperature.new({c: temp})
end

def self.from_fahrenheit(temp)
    Temperature.new({f: temp})
end
victorkt
  • 13,992
  • 9
  • 52
  • 51
  • wow, thanks! since I know that I was using "options hash" (which is another thing I had to learn to solve this) to solve the first part, I wanted to link the class methods with the options hash but could not figure out a way. I did not know you can create an instance of a class within a class or am I wrong? – Guy Mar 10 '15 at 02:19
  • In general, can I create instance variables inside class methods? what variables can I create in class methods? is it class variables @@? – Guy Mar 10 '15 at 02:30
  • You can actually create any kind of variables inside class methods, but if you define an instance variable there it won't be accessible by its instances (at least not the way you would want). Check [this another answer](http://stackoverflow.com/questions/9311347/using-instance-variables-in-class-methods-ruby) in SO for more details. – victorkt Mar 10 '15 at 02:38