juniorRubyist juniorRubyist - 3 months ago 16
Ruby Question

NoMethodError on perfect code

I have a one file program that I am writing called Fitgem. I have just now added automated tests to it for Travis CI. I have found that in the most previous logs and self testing that there are errors. It says the following error.

fitgem.rb:59:in `full_report': undefined method `steps' for FitbitAccount:Class (NoMethodError)
from fitgem.rb:75:in `<main>'


It is supposed to
puts
out a report about your stats.

Please find my code at http://github.com/juniorRubyist/fitgem to help me. The file is
fitgem.rb
.

Could anyone find the problem/solution?
Thanks.

Answer

It looks like you're trying to call an instance method on your class type, not your class instance. This is your code now:

class FitbitAccount
  ...

  def steps
    @response = self.class.get("https://#{@@base_uri}/#{@user_id}/activities/steps/date/today/1d/1min.json",
      :headers => @authorization_header)
    @parsed_response = MultiJson.load(@response.body)
    @parsed_response = @parsed_response["activities-steps"][0]["value"].to_i
  end

  def full_report
    # Full Report
    puts "Full Report:\n-------"
    puts "#{self.class.steps} steps"
    puts "#{self.class.distance} miles"
    puts "#{self.class.floors} stairs climbed"
    puts "#{self.class.cals_out} calories burned"
  end
end

self.class returns the FitbitAccount type and does not refer to the current class object. Since your methods are currently instance methods, meaning you need to assign an object to FitbitAccount.new before you can use them, you are receiving the NoMethodError. To fix this error, you could replace all self.class statements with simply self in your FitbitAccount class, like so:

class FitbitAccount
  ...

  def steps
    @response = self.get("https://#{@@base_uri}/#{@user_id}/activities/steps/date/today/1d/1min.json",
      :headers => @authorization_header)
    @parsed_response = MultiJson.load(@response.body)
    @parsed_response = @parsed_response["activities-steps"][0]["value"].to_i
  end

  def full_report
    # Full Report
    puts "Full Report:\n-------"
    puts "#{self.steps} steps"
    puts "#{self.distance} miles"
    puts "#{self.floors} stairs climbed"
    puts "#{self.cals_out} calories burned"
  end
end

If you wanted your current code to work, you can make your methods class methods instead of instance methods by adding the self keyword before the method name. Here's an example:

class FitbitAccount
  ...

  def self.steps
    @response = self.class.get("https://#{@@base_uri}/#{@user_id}/activities/steps/date/today/1d/1min.json",
      :headers => @authorization_header)
    @parsed_response = MultiJson.load(@response.body)
    @parsed_response = @parsed_response["activities-steps"][0]["value"].to_i
  end

  def self.full_report
    # Full Report
    puts "Full Report:\n-------"
    puts "#{self.class.steps} steps"
    puts "#{self.class.distance} miles"
    puts "#{self.class.floors} stairs climbed"
    puts "#{self.class.cals_out} calories burned"
  end
end

I would not advise you to make them class methods, though, because then they would not be tied to a specific instance, and it seems you want to be able to create multiple instances of FitbitAccount.

Comments