Reputation: 115
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
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
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