alightholder
alightholder

Reputation: 115

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.

Upvotes: 2

Views: 927

Answers (2)

Mark Reed
Mark Reed

Reputation: 95315

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

Upvotes: 2

Zajn
Zajn

Reputation: 4088

I'm not 100% sure about this, but I believe this is a precedence issue.

Since operators always have precedence over methods, the %= 2 is being evaluated before the seconds getter method is being evaluated. This is what would be causing the NoMethodError, I believe.

This would also explain why using @seconds works, since you're referencing the instance variable directly and not using the getter method that attr_accessor creates behind the scenes.

As a side note, I think it makes more sense to use the instance variable in this case from a class design perspective.

Edit: What I said above can't be correct, because methods such as this work:

class Book
  attr_accessor :title, :length

  def midpoint
    length / 2
  end
end

I think it's much more simple that that, actually. I assume that %= works the same other assignment operators like += in that writing seconds %= 60 is the same as writing seconds = seconds % 60.

What is likely happening then is that since you're assigning something to seconds, ruby interprets that as a new local variable called seconds. When the %= is "expanded" into seconds = seconds % 60, the seconds on the right-hand side is interpreted as the same local variable, which is currently nil. Hence, the NoMethodError.

Upvotes: 2

Related Questions