hancho
hancho

Reputation: 1437

Declaring instance variables outside of initialize

I'm trying to convert Celsius temperatures to Fahrenheit and vice versa. As in the following, instance variable @temperature is defined in the methods celsius= and fahrenheit= respectively.

class Temperature
  def self.ctof(temp)
    (temp * 9 / 5.0) + 32
  end

  def self.ftoc(temp)
    (temp - 32) * (5 / 9.0)
  end

  def initialize(options)
    if options[:f]
      self.fahrenheit = options[:f]
    else
      self.celsius = options[:c]
    end
  end

  def fahrenheit=(temp)
    @temperature = self.class.ftoc(temp)
  end

  def celsius=(temp)
    @temperature = temp
  end

  def in_fahrenheit
    self.class.ctof(@temperature)
  end

  def in_celsius
    @temperature
  end
end

It is confusing to me because I've never seen instance variables defined outside of the initialize method. I'm hoping someone can help me understand what is going on here.

Upvotes: 0

Views: 672

Answers (2)

Stefan
Stefan

Reputation: 114178

It is perfectly fine to set instance variables outside of initialize. This is exactly what setters do. You probably already know attr_accessor – when calling attr_accessor :foo, it creates a getter foo and a setter foo= for the instance variable @foo, i.e. two methods equivalent to:

def foo
  @foo
end

def foo=(value)
  @foo = value
end

In your code, in_celsius and celsius= and just that: getters and setters for @temperature.

But your code does indeed look a bit convoluted. I think this is because Temperature has to handle both, Fahrenheit and Celsius. You can simplify it by providing separate classes for each temperature scale:

class Celsius
  attr_reader :value

  def initialize(value)
    @value = value
  end

  def to_celsius
    self
  end

  def to_fahrenheit
    Fahrenheit.new((value * 9 / 5.0) + 32)
  end
end

class Fahrenheit
  attr_reader :value

  def initialize(value)
    @value = value
  end

  def to_celsius
    Celsius.new((value - 32) * (5 / 9.0))
  end

  def to_fahrenheit
    self
  end
end

Now each class has a single instance variable @value which is being set within initialize.

temperature = Celsius.new(0)
#=> #<Celsius:0x007fb83d8b33a8 @value=0>

temperature.to_fahrenheit
#=> #<Fahrenheit:0x007fb83d8b3128 @value=32.0>

Upvotes: 0

Amadan
Amadan

Reputation: 198314

When you call Temperature.new(c: 0), this will set the celsius= accessor, which sets the instance variable @temperature (which is meant to always be in Celsius) to 0.

When you call Temperature.new(f: 32), this will set the fahrenheit= accessor, which sets the instance variable @temperature to Temperature.ftoc(32), or 0.0.

Calling in_celsius simply returns @temperature, or 0 in the example.

Calling in_fahrenheit returns Temperature.ctof(0), or 32.0.

There is nothing magical about an instance variable being defined outside the constructor. The key point is that it is a variable that is available throughout the instance methods.

Upvotes: 1

Related Questions