I'm implementing a Calculator DSL in Ruby. The code is given below. It gives me an error stating that '+' in total = total + number is not defined. What could be the error? Also, could there be any problem in the initialize function that is causing it?
class Calculator
attr_accessor :total
def initialize(&block)
self.total = 0
instance_eval(&block)
return total
end
def add(number)
total = total + number
end
def subtract(number)
total -= number
end
def multiply(number)
total *= number
end
def divide(number)
total /= number
end
end
h = Calculator.new do
add 3
add 5
end
The error message is -
calculator_dsl.rb:10:in `add': undefined method `+' for nil:NilClass (NoMethodError)
from calculator_dsl.rb:27:in `block in <main>'
from calculator_dsl.rb:5:in `instance_eval'
from calculator_dsl.rb:5:in `initialize'
from calculator_dsl.rb:26:in `new'
from calculator_dsl.rb:26:in `<main>'
Short answer: name clash (local variable vs method)
Long answer:
def add(number)
puts defined?(total)
total = (puts defined?(total); total + number)
end
This code outputs
method
local-variable
NoMethodError: undefined method `+' for nil:NilClass
Right before this line
total = total + number
a new local variable total is created which shadows the method from outer scope. It is also set to nil which explains the error you're getting.
To avoid creation of new local var, use self
self.total = total + number
# or
self.total += number
Related
Why can't I apply an operator to an instance variable using it's attr_accessor or attr_writer?. If I try to run this code:
class Numbers
attr_accessor :number1
def initialize
#number1 = 15
end
def subtract
number1 -= 1
p number1
end
end
num = Numbers.new
num.subtract
I get this error:
Traceback (most recent call last):
1: from ex.rb:15:in `<main>'
ex.rb:9:in `subtract': undefined method `-' for nil:NilClass (NoMethodError)
My question, how could I subtract a number from #number1 without having to explicitly putting the #, just calling the attribute.
It is not an instance variable here it is a local one:
def subtract
number1 -= 1
p number1
end
To call an instance variable without # use self.number1.
def subtract
self.number1 -= 1
p number1
end
I've redone the question and included the full code for both files. In the touch_in method I am trying to instantiate a Journey class in the variable called 'journey'.
require_relative 'journey'
class Oystercard
MAXIMUM_BALANCE = 90
MINIMUM_BALANCE = 1
MINIMUM_CHARGE = 1
def initialize
#balance = 0
#journeys = {}
end
def top_up(amount)
fail 'Maximum balance of #{maximum_balance} exceeded' if amount + balance > MAXIMUM_BALANCE
#balance += amount
end
def in_journey?
#in_journey
end
def touch_out(station)
deduct(MINIMUM_CHARGE)
#exit_station = station
#in_journey = false
#journeys.merge!(entry_station => exit_station)
end
def touch_in(station)
fail "Insufficient balance to touch in" if balance < MINIMUM_BALANCE
journey = Journey.new
#in_journey = true
#entry_station = station
end
attr_reader :journeys
attr_reader :balance
attr_reader :entry_station
attr_reader :exit_station
private
def deduct(amount)
#balance -= amount
end
end
The Journey file is as follows:
class Journey
PENALTY_FARE = 6
MINIMUM_CHARGE = 1
def initialize(station = "No entry station")
#previous_journeys = {}
end
def active?
#active
end
def begin(station = "No entry station")
#active = true
#fare = PENALTY_FARE
#entry_station = station
end
def finish(station = "No exit station")
#active = false
#fare = MINIMUM_CHARGE
#exit_station = station
#previous_journeys.merge!(entry_station => exit_station)
end
attr_reader :fare
attr_reader :previous_journeys
attr_reader :entry_station
attr_reader :exit_station
end
I think that the 'touch_in' method should create a 'journey' variable that I called methods on, such as 'finish(station)' or 'active?' etc. When I attempt to do this in IRB I am given the following error:
2.6.3 :007 > journey
Traceback (most recent call last):
4: from /Users/jamesmac/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `<main>'
3: from /Users/jamesmac/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `load'
2: from /Users/jamesmac/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):7
NameError (undefined local variable or method `journey' for main:Object)
I'm aware that much of the code above is sloppily written and there are probably other bits, beside the 'journey' issue, where it's just incorrect. Please let me know if this is the case, the more I'm told the better.
Apologies to anyone who attempted to help me on my first attempt, as I say I'm still getting used to SO and was trying to make the post easier to read.
class Journey
# ...
def initialize
puts "Journey initialized"
# ...
end
# ...
end
require_relative 'journey'
class Oystercard
def initialize
end
# ...
def touch_in(station)
journey = Journey.new
# ...
end
end
Oystercard.new.touch_in("station")
stack_question$ ruby oystercard.rb
Journey initialized
It works fine - are you having some issue with this that is beyond the scope of the question?
Why do increment_v1 and_v3 work, but increment_v2 and _v4 don't (v2 returns the correct value, but doesn't change the #counter, v4 fails with "NoMethodError (undefined method `+' for nil:NilClass)")
class MyClass
attr_accessor :counter
def initialize
#counter = 0
end
def increment_v1
#counter = counter + 1
end
def increment_v2
counter = #counter + 1
end
def increment_v3
#counter += 1
end
def increment_v4
counter += 1
end
end
I expect all of these methods to have the same outcome (increase the #counter value and return the increased number). It has the same error if I replace attr_accessor with attr_reader and attr_writer. I feel like I may be misunderstanding something about the attr_* methods.
Here is what it looks like in the console:
2.6.3 :026 > a = MyClass.new
=> #<MyClass:0x00000000018d7240 #counter=0>
2.6.3 :027 > a.increment_v1
=> 1
2.6.3 :028 > a
=> #<MyClass:0x00000000018d7240 #counter=1>
2.6.3 :029 > a.increment_v2
=> 2
2.6.3 :030 > a
=> #<MyClass:0x00000000018d7240 #counter=1>
2.6.3 :031 > a.increment_v3
=> 2
2.6.3 :032 > a
=> #<MyClass:0x00000000018d7240 #counter=2>
2.6.3 :033 > a.increment_v4
Traceback (most recent call last):
5: from /home/guin/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `<main>'
4: from /home/guin/.rvm/rubies/ruby-2.6.3/bin/irb:23:in `load'
3: from /home/guin/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):33
1: from (irb):23:in `increment_v4'
NoMethodError (undefined method `+' for nil:NilClass)
Running a.counter += 1 from outside the class works as I expect. Do I have to specify self.counter += 1 when I am inside the class? Why? It even works if with self.counter = counter + 1. What is going on?
As you have shown, you can always access an attribute directly using the instance variable (#counter). However your issue here is relating to the getter/setter methods generated by attr_accessor.
The getter method does not require self unless you have a local variable with the same name. Setter methods are different. You always need to use self with setters.
For example:
def test_method
# directly set instance var. this will always work
#counter = 1
# define local variable with same name.
# this does not call the setter because you don't use self
counter = 0
puts counter
# prints 0
# The getter method is never called because you have a local variable
# with the same name.
puts self.counter
# prints 1
# you can force the getter to be called by using self
end
I think the idiomatic way to write your method would be:
def increment_v5
self.counter += 1
end
However you could also write it like this:
def increment_v6
self.counter = counter + 1
# \ calls getter
end
and there are many other ways to write it.
i can't figure out what's wrong with my code, can you help me please?
this is constructor of my class:
def initialize(hash_table_size)
#size = hash_table_size
#table = Array.new(hash_table_size) { LinkedList.new }
end
this is method in that class:
def to_a
arr = Array.new
#table.each { |list| list.each { |o| arr << o } }
arr
end
this is my "each" method in LinkedList class:
def each
if #length > 0
item = #head
begin
yield item.object
item = item.next
end until item.nil?
end
end
and this is what i get from unittest:
1) Error:
test_initial_size_3(HashSetTest):
NoMethodError: undefined method `each' for 3:Fixnum
C:/Users/Ajax/My Documents/Aptana Studio 3 Workspace/alg_du1_jan_svec/hash_set.rb:34:in `block in to_a'
C:/Users/Ajax/My Documents/Aptana Studio 3 Workspace/alg_du1_jan_svec/hash_set.rb:34:in `each'
C:/Users/Ajax/My Documents/Aptana Studio 3 Workspace/alg_du1_jan_svec/hash_set.rb:34:in `to_a'
C:/Users/Ajax/My Documents/Aptana Studio 3 Workspace/alg_du1_jan_svec/hash_set_test.rb:14:in `test_initial_size_3'
1 tests, 3 assertions, 0 failures, 1 errors, 0 skips
It means that LinkedList.new in the method initialize is returning 3, which becomes an element of #table, and is substituted into the block variable list of the method to_a.
isn't + an operator? why would it not be defined?
here's my code:
Class Song
##plays = 0
def initialize(name, artist, duration)
#name = name
#artist = artist
#duration = duration
#plays = 0
end
attr_reader :name, :artist, :duration,
attr_writer :name, :aritist, :duration
def play
#plays += 1
##plays += 1
"This Song: ##plays play(s). Total ###plays plays."
end
def to_s
"Song: ##name--##artist (##duration)"
end
end
First, this code doesn't even run: class on Line 1 needs to be spelled with a lowercase c, and you can't have a comma after the last item in a statement (your attr_reader line). I don't get a NoMethodError after fixing those and running Song.new or Song#play or Song#to_s.
Anyway, you will always get that NoMethodError when you try adding anything to a nil value:
>> nil + 1
NoMethodError: undefined method `+' for nil:NilClass
from (irb):1
>> nil + nil
NoMethodError: undefined method `+' for nil:NilClass
from (irb):2
>> # #foo is not defined, so it will default to nil
?> #foo + 2
NoMethodError: undefined method `+' for nil:NilClass
from (irb):4
So you might be trying to add something to an uninitialized instance variable... or it could be anything. You always need to post full, minimal code to duplicate an error if you want to be helped properly.
+ is defined on numbers (among other things). However, as the error message says, it is not defined on nil. This means you can't do nil + something and why would you?
That being said, you're actually not calling nil + something anywhere in the code you've shown (you're initializing both #plays and ##plays to 0, and you're not setting them to nil at any point). And as a matter of fact your code runs just fine once you remove the two syntax error (Class should be class and there should be no comma after :duration). So the error is not in the code you've shown.
maybe you should include ##plays = 0 in your initialize method?