Search code examples
rubynomethoderrorfixnum

undefined method `%' for nil:NilClass


I am getting "NoMethodError: undefined method `%' for nil:NilClass" for the following code block:

class Timer
    attr_accessor :seconds

    def initialize
        @seconds = 0
    end

    def time_string

        if seconds < 10
            return "00:00:0" + seconds.to_s
        elsif seconds < 60
            return "00:00:" + seconds.to_s
        elsif seconds < 540
            minutes = seconds / 60
            seconds %= 60
            #seconds = seconds - (minutes * 60)
            return "00:0" + minutes.to_s + ":0" + seconds.to_s
        end
    end

    def timer
        @timer          
    end
end

I know that 'seconds' is a Fixnum because I get NoMethod: Fixnum error when I try to #puts seconds without a #to_s. Also, the "/" operation on 'seconds' in the preceding line works fine. So why am I getting a NoMethod:nilclass error message?

Why am I even getting an error message? Shouldn't "%" work everywhere "/" does?

The following code works:

        if seconds < 10
            return "00:00:0" + seconds.to_s
        elsif seconds < 60
            return "00:00:" + seconds.to_s
        elsif seconds < 540
            minutes = @seconds / 60
            seconds = @seconds % 60
            return "00:0" + minutes.to_s + ":0" + seconds.to_s
        end

It has something to do with instance variables, and my not understanding instance variables. would love to know how the nil got in there.


Solution

  • It's an interaction between methods called on self, local variables, and Ruby's lack of syntactic distinction between those things.

    If you change the line to this:

             self.seconds %= 60
    

    Then it works fine.

    The problem is that when Ruby sees an assignment to an unqualified name, it creates a local variable with that name rather than looking for an accessor.

    Here's a simple demonstration:

    irb(main):001:0> def foo=(n)
    irb(main):002:1>  puts "Calling foo!"
    irb(main):003:1>  @foo=n
    irb(main):004:1> end    #=> nil
    irb(main):005:0> foo=1    #=> 1
    irb(main):006:0> @foo    #=> nil
    irb(main):007:0> self.foo=2
    Calling foo!
    => 2
    irb(main):008:0> @foo    #=> 2