This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
I am doing the beginning ruby book and this is what I have for my code, which gives the following error:
in 'show_current_description': undefined method 'full_description' for nil:NilClass (NoMethodError)
The code is as follows. Any help is appreciated. Thanks!
class Dungeon
attr_accessor :player
def initialize(player_name,start_location)
#player=Player.new(player_name,start_location)
puts #player.location
#rooms=[]
show_current_description
end
def show_current_description
#rm=find_room_in_dungeon(#player.location)
#rm.full_description
end
def find_room_in_dungeon(reference)
#rooms.detect{|room| room.reference == reference; puts room.full_description}
end
def add_room(reference,name,description,connections)
#rooms << Room.new(reference,name,description,connections)
end
Player=Struct.new(:name,:location)
class Room
attr_accessor :reference, :name, :description, :connections
def initialize(reference,name,description,connections)
#reference=reference
#name=name
#description=description
#connections=connections
end
def full_description
"You are in " + #description
end
end
end
d=Dungeon.new("Good Man",:small_cave)
d.add_room(:small_cave,"Small Cave","This is a small claustrophobic cave", {:east => :largecave})
d.add_room(:large_cave,"Large Cave","This is a large cavernous cave", {:west => :smallcave})
puts d.player.name
d.show_current_description
The error that you posted means that this line...
#rm.full_description
...is trying to call full_description on an object that has no such method, because the #rm object is nil. From that we can deduce that the previous line...
#rm=find_room_in_dungeon(#player.location)
...set #rm to nil as the result of the find_room_in_dungeon method. If we look at that method...
#rooms.detect{|room| room.reference == reference; puts room.full_description}
...we see one problem. The detect method uses the return value of the block to figure out if a room should be used or not. However, the puts method (which is the last in the block, and hence the return value of the block) always returns nil. Hence, #rooms.detect will never find anything. So let's fix that by removing the puts altogether:
#rooms.detect{|room| room.reference == reference }
That should help, but it didn't solve the problem. We're still getting nil from this method. Let's look deeper.
First, let's validate our assumptions about what the precondition for this method is. Before that changed line, we add p #rooms, reference to the find_room_in_dungeon method. Running the code now, we see this output:
[]
:small_cave
Ah-ha! So that's the problem. We're looking for the small_cave room, but our list of rooms is empty! We know that we're calling d.add_room at the bottom of the script...why isn't it working?
Let's see where we are when this code breaks. Here's the full backtrace from the error message:
C:/tmp.rb:13:in `show_current_description': undefined method `full_description' for nil:NilClass (NoMethodError)
from C:/Users/gkistner.NVIDIA.COM/Desktop/tmp.rb:8:in `initialize'
from C:/Users/gkistner.NVIDIA.COM/Desktop/tmp.rb:43:in `new'
from C:/Users/gkistner.NVIDIA.COM/Desktop/tmp.rb:43:in `<main>'
Ah-HA! The problem is that we call show_current_description as part of our initialize method, before we've had a chance to add rooms to the dungeon.
Here are some ways we could fix this:
Change the Dungeon constructor to accept a list of rooms as part of initialization, so we can do this all at once. Here are three different ways one could do it:
# Method number one, accept an array of rooms
class Dungeon
def initialize(name,start,rooms=[])
#player = ...
#rooms = rooms
yield(self) if block_given?
show_current_description
end
end
d = Dungeon.new( "Bob", :small_cave, [
Dungeon::Room.new(...),
Dungeon::Room.new(...)
] )
# Method 2: Allow an initializer block
class Dungeon
def initialize(name,start)
#player = ...
#rooms = []
yield(self) if block_given?
show_current_description
end
end
d = Dungeon.new( "Bob", :small_cave ) do |dungeon|
dungeon.add_room(...)
dungeon.add_room(...)
end
# Method 3 (experts only!): Initialize block runs in scope
class Dungeon
def initialize(name,start,&initializer)
#player = ...
#rooms = []
instance_eval(&initializer) if initializer
show_current_description
end
end
d = Dungeon.new( "Bob", :small_cave ) do
add_room(...)
add_room(...)
end
Don't show the current description as part of the initializer. Just remove that line, and when running show the description when construction is complete:
d = Dungeon.new(...)
d.add_room(...)
d.add_room(...)
d.show_current_description
Make your show_current_description method more robust, so that it can handle the nil case:
def show_current_description
#rm = find_room_in_dungeon(#player.location)
puts #rm.full_description if #rm
end
You could choose to do 1 or 2, but I'd suggest also doing 3.
The 4th line from the bottom, #rooms :largecave}), is a syntax error. The real code surely looks different.
Anyway, "undefined method full_description for nil:NilClass" means that #rm, returned by find_room_in_dungeon, is nil.
Did you remove the puts in the #rooms.detect {...} ?
Remove it, and it should work.
Related
Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result
I am creating a code for a small game of 4 horses running a set distance across my terminal. I have it to where it is outputting my horses that I have added and my users horse, but when I go to my next class to build the race it self, I keep getting method undefined errors. I searched for something similar but couldn't find anything. learningruby.com has some roundabout answers to it, but not showing me what im missing.
class Horses
##list_of_horses = []
attr_accessor :name
attr_accessor :position
def initialize
self.name = nil
self.position = 0
end
def self.add_horse(*horse_variables)
horse = Horses.new
horse.name = horse_variables[0]
##list_of_horses.push horse
end
def self.add_user(*user_variables)
add_user = Horses.new
add_user.name = user_variables[0]
##list_of_horses.push add_user
end
def self.display_data
puts "*" * 60
##list_of_horses.each do |racer|
print "-" * racer.position
puts racer.name
end
end
def move_forward
self.position += rand(1..5)
end
def self.display_horses
##list_of_horses
end
end
horse1 = Horses.add_horse ("Jackrabbit")
horse2 = Horses.add_horse ("Pokey")
horse3 = Horses.add_horse ("Snips")
user1 = Horses.add_user ("Jim")
Horses.display_data
Now when I run just this file, It will give me the printout in my terminal of
Jackrabbit
Pokey
Snips
Jim
But when I start trying to call the methods I have created in my Horses class in my next class of Race even outside of the Race class itself, Im returning method undefined.
require_relative 'Horses_class.rb'
no_winner = true
class Race
def begin_race
puts "And the Race has begun!"
end
end
while no_winner == true
puts begin_race
racing = Race.new
racing.Horses.display_data
end
So why am I not allowed to call my other methods? should I be using a splat or is there something more simplistic that im missing? Thank you in advanced.
Jim
Your begin_race method seems to be out of scope when you're calling it. You need to use either the . or the :: (scope) operator to access it.
class Race
def self.begin_race
puts "And the race has begun!"
end
end
Race::begin_race
# or
Race.begin_race
Also, when you call racing.Horses.display_data you must make sure that your Horses class is a sub-class of you racing class. You can not call a sub-class via an object, you must call it through the class constant.
class Race
class Horses
def self.display_data
puts "The Data"
end
end
end
# Access 'display_data'
Race::Horses.display_data
So in this case your require_relative should be within your Race class and your while block should look like
while no_winner == true
Race.begin_race
Race::Horses.display_data
end
Trying to solve this problem,
class Person
def initialize(name)
#name=name
end
def greet(other_name)
puts "Hi #{other_name}, my name is #{name}"
end
end
initialize("ak")
greet("aks")
but I am getting the error like:
ArgumentError: wrong number of arguments calling `initialize` (1 for 0)
I don't understand what is asking here, if its just the argument then why the error is like (1 for 0). can someone help me understand this problem.
Look at this code:
class Person
attr_reader :name
def initialize( name )
puts "Initializing Person instance #{object_id}"
#name = name
end
def greet( name )
puts "Hi #{name}, I'm #{name()}"
end
end
When you wrote initialize without explicit receiver:
initialize( "ak" )
It was only a matter of luck that your message was recognized. Look who has responded:
method( :initialize ).owner
#=> BasicObject
BasicObject, the foremother of all Object instances, herself responded to your call, by scolding you about wrong number of arguments, because:
method( :initialize ).arity
#=> 0
Not only this method does not expect any arguments, but also you are not expected to call it at all. In fact, you are not expected to call #initialize on any object by yourself, save for exceptional situations. Class#new method handles calling of Person#initialize method for you:
A = Person.new( 'Abhinay' )
Initializing Person instance -605867998
#=> #<Person:0xb7c66044 #name="Abhinay">
Person.new handled creation of a new instance and automatically called its #initialize method. Also, #initialize method is created private, even if you did not specify it explitcitly. The technical term for such irregular behavior is magic. Person#initialize is magically private:
A.initialize( 'Fred' )
NoMethodError: private method `initialize' called for #<Person:0xb7c66044 #name="Abhinay">
You cannot just reinitialize yourself to 'Fred', you know. All other methods are public unless prescribed otherwise:
A.greet "Arup"
Hi Arup, I'm Abhinay
#=> nil
You need to call the methods on the object (not just call the methods) and initialize is automatically called when creating a new object:
p = Person.new("ak")
p.greet("aks") #=> "Hi aks, my name is ak"
The problem is that to create new object you need to call method new on class, and not initialize on the object.
So code looks like this:
p = Person.new("John")
Please, take a look at code below:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def greet(other_name)
puts "Hi #{other_name}, my name is #{name}"
end
end
person = Person.new("ak")
person.greet("aks")
#=> Hi aks, my name is ak
I a trying to follow a tutorial with Ruby, but am getting very confused. Everywhere I find seems to say that defining an instance variable is done like so;
class Example
def fun
# CODE
end
end
e = Example.new
e.fun # <- Will run your code
Bu I really really don't get why this isn't working
class Example
include Enumerable
def initialise
#members = ["a", "b"]
end
def each
#members.each do |member|
yield member
end
end
end
When I call
e = Example.new
e.each do |elmt|
puts elmt
end
I get the error
NoMethodError: undefined method `each' for nil:NilClass
Can anybody help me figure out how to get this working. I cant find out what's wrong, below are 3 of the many sources that lead me to believe this should work. I am obviously doing something wrong, but I just cant see it
sources;
http://ruby.about.com/od/advancedruby/ss/Using-The-Enumerable-Module.htm
http://www.railstips.org/blog/archives/2009/05/11/class-and-instance-methods-in-ruby/
Book: Engineering Software as a Service
You have a typo. It's initialize, not initialise. Your #members instance var was never assigned to, that's why it's nil.
I am getting an error when I create a class of Stats and another a container class. The error is
test.rb:43:in `<main>' undefined method `each' for #<Boyfriends:0x2803db8 #boyfriends=[, , , ]> (NoMethodError)
which makes absolute sense because that class indeed does not contain that method, but should ruby search the parents and grandparents classes for the method? The script displays the desired output; it just inlays the error with the output like so
test.rb:43:in `<main>'I love Rikuo because he is 8 years old and has a 13 inch nose
I love dolar because he is 12 years old and has a 18 inch nose
I love ghot because he is 53 years old and has a 0 inch nose
I love GRATS because he is unknown years old and has a 9999 inch nose
: undefined method `each' for #<Boyfriends:0x2803db8 #boyfriends=[, , , ]> (NoMethodError)
Here is the code
class Boyfriends
def initialize
#boyfriends = Array.new
end
def append(aBoyfriend)
#boyfriends.push(aBoyfriend)
self
end
def deleteFirst
#boyfriends.shift
end
def deleteLast
#boyfriends.pop
end
def [](key)
return #boyfriends[key] if key.kind_of?(Integer)
return #boyfriends.find { |aBoyfriend| aBoyfriend.name }
end
end
class BoyfriendStats
def initialize(name, age, nose_size)
#name = name
#age = age
#nose_size = nose_size
end
def to_s
puts "I love #{#name} because he is #{#age} years old and has a #{#nose_size} inch nose"
end
attr_reader :name, :age, :nose_size
attr_writer :name, :age, :nose_size
end
list = Boyfriends.new
list.append(BoyfriendStats.new("Rikuo", 8, 13)).append(BoyfriendStats.new("dolar", 12, 18)).append(BoyfriendStats.new("ghot", 53, 0)).append(BoyfriendStats.new("GRATS", "unknown", 9999))
list.each { |boyfriend| boyfriend.to_s }
Which makes absolute sense because that class indeed does not contain that method, but as I've been reading should ruby search the classes parents and grandparents for the method?
That's correct, but you didn't declare any superclasses so the superclass will be Object. Which also doesn't have an each method.
If you want an enumerable method, you'll have to define it yourself - you'll probably want to iterate over the array.
In that case, you could just define an own each method that just passes the passed block down to the arrays each method:
class Boyfriends
def each(&block)
#boyfriends.each(&block)
end
end
The &block here let's you capture a passed block by name. If you're new to ruby, this probably doesn't mean much to you, and explaining how it works is somewhat beyond the scope of this question. The accepted answer in this Question does a pretty good job of explaining how blocks and yield work.
once you got an each method, you can also pull in Enumerable for a number of convenience methods:
class Boyfriends
include Enumerable
end
Also, to_s is a method that should return a string, so you should remove the puts in BoyfriendStats#to_s.