0

I have a piece of code that uses class variables. I've read that class variables should generally be avoided in Ruby.

The class variables are @@cost and @@kwh.

How can I rewrite the following without using class variables?

class Device
 attr_accessor :name, :watt

 @@cost = 0.0946

 def initialize(name, watt)
   @name = name
   @watt = watt
 end

  def watt_to_kwh(hours)
    @@kwh = (watt / 1000) * hours
  end

  def cost_of_energy
    puts "How many hours do you use the #{self.name} daily?"
  hours = gets.chomp.to_i
    self.watt_to_kwh(hours)
    daily_cost = @@kwh * @@cost
    montly_cost = daily_cost * 30
    puts "Dayly cost: #{daily_cost}€"
    puts "montly_cost: #{montly_cost}€"
  end
end
2
  • Why is @@kwh a class variable? Commented Jun 11, 2017 at 13:30
  • 1
    I believe montly_cost should monty_cost, assuming it concerns Monty Python's Flying Circus. Commented Jun 11, 2017 at 18:55

2 Answers 2

5

@@cost behaves more like a constant (i.e. it won't change during runtime), so you should use one instead:

COST = 0.0946

@@kwh should be an instance variable, since it is used only within the instantiated object, so you could use @kwh instead:

@kwh = (watt / 1000) * hours

And daily_cost = @@kwh * @@cost will become:

daily_cost = @kwh * COST

That will avoid the use of class variables, but you could also eliminate @kwh altogether since you don't use it anywhere else.

So, instead of:

def watt_to_kwh(hours)
  @kwh = (watt / 1000) * hours
end

You could just do:

def watt_to_kwh(hours)
  (watt / 1000) * hours
end

And use it like this in cost_of_energy method:

def cost_of_energy
  puts "How many hours do you use the #{self.name} daily?"
  hours = gets.chomp.to_i
  daily_cost = watt_to_kwh(hours) * COST
  montly_cost = daily_cost * 30
  puts "Dayly cost: #{daily_cost}€"
  puts "montly_cost: #{montly_cost}€"
end
Sign up to request clarification or add additional context in comments.

4 Comments

I've actually to make both instance variables, before posting, but it didn't worked, I was getting an *': nil can't be coerced into Integer TypeError. Now I see that making Cost a local variable is the reason i'm getting the error. But why? Why @kwh works and @cost doesn't?
@johnlock1 How did you set @cost = 0.0946? It should be set inside initialize method (or any other instance method).
@gery Ok, so in order to use an instance variable I have to set it inside a method? I haven't understood that, thanks for clearing it out for me!
@johnlock1 Yes, otherwise it won't be available to the instantiated object.
1

Try this.

class Device
  singleton_class.send(:attr_accessor, :cost_per_kwh)

  def initialize(name, watts)
    @name = name
    @watts = watts
  end

  def daily_cost(hours_per_day)
    self.class.cost_per_kwh * kwh_per_day(hours_per_day)
  end

  def monthly_cost(hours_per_day)
    30 * daily_cost(hours_per_day)
  end

  private

  def kwh_per_day(hours_per_day)
    hours_per_day * @watts / 1000
  end
end

singleton_class.send(:attr_accessor, :cost_per_kwh) creates a setter and getter for the class instance variable @cost_per_kwh.

First, obtain and save the cost per kwh, which will be used in the calculation of cost for all devices of interest.

puts "Please enter the cost per kwh in $"    
Device.cost_per_kwh = gets.chomp.to_f

Suppose

Device.cost_per_kwh = 0.0946

Calculate the costs for each device of interest.

puts "What is the name of the device?"
name = gets.chomp

puts "How many watts does it draw?"
watts = gets.chomp.to_f

Suppose

name = "chair"
watts = 20000.0

We may now create a class instance.

device = Device.new(name, watts)
  #=> #<Device:0x007f9d530206f0 @name="chair", @watts=20000.0> 

Lastly, obtain hours per days, the only variable likely to change in future calculations of costs for the given device.

puts "How many hours do you use the #{name} daily?"
hours_per_day = gets.chomp.to_f

Lastly, suppose

hours_per_day = 0.018

then we may compute the costs.

puts "Daily cost: $#{ device.daily_cost(hours_per_day)}"
Daily cost: $0.034056€

puts "Monthly_cost (30 days/month): $#{ 30 * device.daily_cost(hours_per_day) }"
Monthly_cost (30 days/month): $1.0216800000000001

Suppose circumstances change1 and use of the device increases. We need only update hours per day. For example,

puts "How many hours do you use the #{name} daily?"
hours_per_day = gets.chomp.to_f

Suppose now

hours_per_day = 1.5

Then

puts "Daily cost: $#{ device.daily_cost(hours_per_day)}"    
Daily cost: $2.838

puts "Monthly_cost (30 days/month): $#{ 30 * device.daily_cost(hours_per_day) }"
Monthly_cost (30 days/month): $85.14

1 The election of a new president, for example.

1 Comment

Nice! I like singleton_class to avoid the (hardcoded) constant.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.