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.
Related
so I have the following anonymous class definition:
let!(:fake_class) do
Class.new(Integer) do
def initialize(value)
#value = value
end
def ==(other)
#value == other
end
def coerce(other)
[#value, other]
end
def to_s
#value.to_s
end
end
end
But when I do:
fake_class.new 4
I just get undefined method 'new' for #<Class:0x00007fc065377c88>
I've tried doing
define_method :initialize do |value|
#value = value
end
no difference
the only way it responds to new is if I do
class << self
def new(value)
#value = value
end
end
but that obviously won' work as I need it to act like a real class.
Why do I see lots of tutorials using intialize and it working as expected yet it doesn't seem to work for me? Is it becuase i'm defining it in rspec or somthing?
The issue here is nothing to do with rspec, nor anonymous classes.
The problem is that in ruby, you cannot subclass Integer*.
Ruby stores small Integers (formerly known as Fixnums) as immediate values, using some of the low bits of the word to tag it as such, instead of a pointer to an object on the heap. Because of that, you can't add methods to a single "instance" of Integer, and you can't subclass it.
If you really want an "Integer-like" class, you could construct a workaround with a class that has an integer instance variable, and forward method calls appropriately:
class FakeInteger
def initialize(integer)
#integer = integer
end
def method_missing(name, *args, &blk)
ret = #integer.send(name, *args, &blk)
ret.is_a?(Numeric) ? FakeInteger.new(ret) : ret
end
end
* Technically you can, but since you cannot instantiate any objects from it, it's pretty useless :)
Your code is correct but Integer does not respond to .new and so your child class will also not respond to .new.
irb(main):001:0> Integer.new
NoMethodError (undefined method `new' for Integer:Class)
When you call Integer(123) you actually call a global function defined here:
https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3987
https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3178
I'm trying to understand self in Ruby.
In the code pasted below, if I create a new instance of Animal with
fox = Animal.new.name("Fox").color("red").natural_habitat("forest").specie("mammal")
and then call
fox.to_s
It does not do anything if I do not return self in every method.
Why do I need self in every method? Isn't the variable already saved once I create a new Animal?
class Animal
def name(name)
#name = name
self
end
def specie(specie)
#specie = specie
self
end
def color(color)
#color = color
self
end
def natural_habitat(natural_habitat)
#natural_habitat = natural_habitat
self
end
def to_s
"Name: #{#name}, Specie: #{#specie}, Color: #{#color}, Natural Habitat: #{#natural_habitat}"
end
end
This pattern is used infrequently in Ruby, it's much more common in languages like Java and JavaScript, where it's notably rampant in jQuery. Part of the reason why is the verbosity you're describing here, and secondly because Ruby provides a convenient mutator generator in the form of attr_accessor or attr_writer.
One problem with these accessor/mutator dual purpose methods is ambiguity. The implementation you have is incomplete, you're unable to read from them. What you need is this:
def color(*color)
case (color.length)
when 1
#color = color
self
when 0
#color
else
raise ArgumentError, "wrong number of arguments (%d for 0)" % color.length
end
end
That's a whole lot of code to implement something that can be used in two ways:
animal.color('red')
animal_color = animal.color
If you want to use these, you'll need to write your own meta-programming method that can generate them, though I'd highly discourage going down that path in the first place. Use attr_accessor methods and an options Hash.
Here's the equivalent Ruby pattern:
# Up-front assignment via Hash
animal = Animal.new(
name: 'Fox',
color: 'red',
natural_habitat: 'forest',
specie: 'mammal'
)
# Assignment after the fact
animal.color = 'white'
In your example, using self as the return value is convenient. The methods return the instance itself, so that you can call:
fox = Animal.new
fox.name("Fox").color("red").natural_habitat("forest").specie("mammal")
The value of fox.name("Fox") is the instance itself, that's why you can call .color("red") on it.
if the #name method would be implemented without calling self, like so:
def name(name)
#name = name
end
This method would return a string when called.
Animal.new.name #=> returns "name of animal"
This means that
Animal.new.name.specie
would call #specie method on a string object (which probably raises NotImplemented error) instead of object of Animal class which implements the method.
Here's the code:
class Dungeon
attr_accessor :player
def initialize(player_name)
#player = Player.new(player_name)
end
end
Now, if I write:
dungeon = Dungeon.new("Robert")
puts dungeon.player.name
it's obviously going to spit out Robert.
I'm a little new to Ruby, so forgive me if this question is obvious to you all. I'm struggling to wrap my head around it still. I am learning about "instance", "class", and global variables and my question is: How does Ruby know that :player in the code above refers to #player? Why isn't the code written, instead, :#player?
Does that make any sense?
attr_accessor is just a method that defines a public getter/setter. #player is the actual instance variable that stores values. The getter it generates is based on the symbol name, so :player creates these methods:
def player
#player
end
def player=(player)
#player = player
end
If you only want the getter you can use attr_reader. If you only want the setter you can use attr_writer. attr_accessor combines these two methods and gives you both methods.
One thing that is easy to overlook when first learning Ruby is that the construct
attr_accessor :property_name
is just a shorter way of writing getter and setter methods for a class's instance variables:
# getter
def property_name
#property_name
end
# setter
def property_name=( some_value )
#property_name = some_value
end
It's basically an example of the "metaprogramming" support in Ruby, but it feels like something more mysterious.
Also, you do not have to use the symbol style, you could instead have:
attr_accessor "property_name"
The symbol version just "reads better" for most people.
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.
http://gist.github.com/172341 ( stackoverflow was breaking the formatting )
In the following case method name created by Human is not available to Boy. Is my understanding correct that attr_accessor methods are not
available to subclasses. I need to use superclass to access the method added by attr_accessor.
What you're looking for is cattr_accessor which fixes this specific problem:
http://apidock.com/rails/Class/cattr_accessor
Here's your example, fixed:
class Human
def self.age
#age = 50
end
def self.age=(input)
#age = input
end
cattr_accessor :name
self.name = 'human'
end
class Boy < Human
end
puts Human.age
puts Boy.age
puts Human.name
puts Boy.superclass.name
puts Boy.name # => 'human'
Human and Boy are two different objects. Two objects can never share a single instance variable. They do both have the method, but the method will access the appropriate ivar for the object.
Rails class_attribute method would be better in this case.
Rails Guide