Ruby inheritance nil class - ruby

I'm trying to make a game but I am having problems settinga default value for an attribute and having a different default value for each subclass.
Here is the problem:
class Player
attr_accessor :hp
#hp = 2
end
class Harper < Player
#hp = 5
end
bill = Harper.new.hp #=>nil
I'm expecting Harper.new.hp to be 5 but it's showing nil instead, and I don't understand why.

The problem with your initialization is that it exists at the class level. That is, you are creating a class instance variable (confusing?) not an object-instance variable as you expect.
In order to create an instance variable you need to do it in a method run at the instance level, like the initialize method which runs when you create an object with the "new" method.
Example:
class Hello
#world = "World!"
def initialize
#to_be_or_not_to_be = "be!"
end
end
=> :initialize
inst = Hello.new
inst.instance_variables
=> [:#to_be_or_not_to_be]
Hello.instance_variables
=> [:#world]
inst.class.instance_variables
=> [:#world]

You need to place your assignments on an initialize function:
class Player
attr_accessor :hp
def initialize
#hp = 2
end
end
class Harper < Player
def initialize
super ## May not be necessary for now.
#hp = 5
end
end
bill = Harper.new.hp
# => 5

new class method runs instance method initialize, so your code should look like:
class Harper < Player
def initialize
#hp = 5
end
end

Related

How to access an instance variable in another Ruby class?

class Player
attr_accessor :card_pile
def initialize
#bust = false
#card_pile = []
end
def bust?
return #cards.inject(:+) > 21
end
end
I have this Player class and have initazlied card_pile variable
class Game
def initialize
#players = []
end
def playing_game
puts "How many players are playing? "
players_amount = gets.chomp.to_i
(0...players_amount).each do
puts ("What is the players name? ")
name = gets.chomp
#players.push(name)
end
puts #players
player = Player.new
player.initialize
while #card_pile.length < 2 do
new_card = Card.new
#card_pile.push(new_card.value)
end
end
I wish to use this variable in the while loop below. Why cannot this be accessed in the way I am hoping it will be?
The error message is: ``playing_game': private method initialize' called for #<Player:0x007fda53073f48 #bust=false, #card_pile=[]> (NoMethodError)
initialize is called automatically when you make a new instance of a class using Player.new. You don't currently have any arguments being passed in to your initialize method, but you have set the instance variable card_pile with attr_accessor, so you can do this:
player = Player.new
while player.card_pile.length < 2 do
new_card = Card.new
player.card_pile.push(new_card.value)
end

What happens at the background when variables are declared in Ruby?

I would like to know what happens behind the scene when variables are declared in ruby. For example, What differentiates these variables from one another?
#normal variables
name = "John"
#instant variables
#name = "John"
#class variables
##name = "John"
#class instance variables
def self.namer
#name = "John"
end
#constants
NAME = "John"
Normal variables, like name, are local. They're only available in the scope in which they were declared.
class Dog
def set_a_variable
name = "Fido"
end
def show_a_variable
name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> NameError: undefined local variable or method `name'
Instance variables, like #name, belong to the instance of a class, so every instance method for an instance of a class has access to that variable. If not set, nil is assumed.
class Dog
def set_a_variable
#name = "Fido"
end
def show_a_variable
#name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> "Fido"
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> nil # not shared between different instances
Class variables, like ##legs, are accessible by all instances of a class, so every every instance has access to that variable. They're also inherited by sub-classes.
class Animal
def set_a_variable
##legs = 4
end
end
class Dog < Animal
def show_a_variable
##legs
end
end
my_animal = Animal.new
my_animal.set_a_variable
my_dog = Dog.new
my_dog.show_a_variable
=> 4
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> 4
Class instance variables (#name defined in a class method) belong to the specific class, so every instance method has access to that variable, but it's not inherited by child classes.
class Animal
def self.set_a_variable
#legs = 2
end
def self.show_a_variable
#legs
end
def show_a_variable
self.class.show_a_variable
end
end
class Dog < Animal
def self.set_a_variable
#legs = 4
end
end
my_dog = Dog.new
Dog.set_a_variable
my_animal = Animal.new
Animal.set_a_variable
my_dog.show_a_variable
=> 4
Constants are NOT global, but are accessible via scoping anywhere.
class Animal
LEGS = 4
end
class Dog
def show_a_variable
Animal::LEGS
end
end
my_dog = Dog.new
my_dog.show_a_variable
=> 4
Variables are never declared in Ruby. They just spring into existence when they are first assigned.
Their scope differentiates them.

Avoiding initialize method in ruby

I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end

Inheritance initialize parameters

I was wondering how to properly initialize the subclass "Computer." I want it to inherit the attributes in initialize in the Game class, except for #start, which is a method. I am also unsure of how to handle parameters in the initialize method in this case. Does anyone know an elegant way to rephrase it? Thanks.
class Game
attr_reader :input, :clues
def initialize
colors = %w(R O Y G I V)
code = []
all = ''
count = 0
start
end
def start
...
end
def ask_input
...
end
class Computer < Game
attr_reader :input, :clues
def initialize
colors = %w(R O Y G I V)
code = []
all = ''
count = 0
ask_input
computer_turn
end
.....
end
I want it to inherit the attributes in initialize in the Game class, except for #start, which is a method.
All attributes and methods will be inherited. You did this correctly with:
class Computer < Game
You don't need the attr_reader because it is inherited from Game.
I am also unsure of how to handle parameters in the initialize method in this case.
You can do something like the following. It takes an input as the parameter. Consider:
computer = Computer.new( :foo )
After the computer is initialized, it's input is equal to :foo.
class Computer < Game
def initialize input
#input = input
...
See:
computer.input
=> :foo
I am also unsure of how to handle parameters in the initialize method in this case
You just
Add super in the initializer of a sub-class to call the initializer of its super-class.
And for sure, all instance variables should have # char at beginning to make they usable though all instance menthods.
Also remove attr_reader from the Computer class, because it will be inherited from Game class
I want it to inherit the attributes in initialize in the Game class, except for #start, which is a method
Finally, to avoid call the method #start of Game class, I think that you just need to override it in Computer class
Result code
class Game
attr_reader :input, :clues
def initialize
#colors = %w(R O Y G I V)
#code = []
#all = ''
#count = 0
start
end
def ask_input
# sample value for #input
#input = 'sample input'
end
def start
puts "start"
end
end
class Computer < Game
#attr_reader :input, :clues
def initialize
super
ask_input
computer_turn
end
def start
# Do nothing
end
def computer_turn
puts "computer_turn"
p #colors
end
end
comp = Computer.new
# The string "start" is not puts here because Game#start is not called
=> computer_turn
=> ["R", "O", "Y", "G", "I", "V"]
comp.input
=> "sample input"
Since you don't want the method start, just eliminate it from your Game class so that it wouldn't appear on your subclasses. Something like :
class Game
attr_reader :input, :clues
def initialize
colors = %w(R O Y G I V)
code = []
all = ''
count = 0
(Insert what start does here)
end
def ask_input
...
end
Then, just override the initialize of your Computer subclass with:
def initialize
colors = %w(R O Y G I V)
code = []
all = ''
count = 0
(insert other functionalities)
end
You can also eliminate the redundant attr_reader since it has been inherited from Game

How do I write a writer method for a class variable in Ruby?

I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?

Resources