Passing argument in instance variable in Ruby gets error - ruby

class Account
attr_reader :name
attr_reader :balance
def initialize(name, balance=100)
#name = name
#balance = balance
end
public
def display_balance(pin_number)
if pin_number == pin
puts "Balance: $#{#balance}."
else
puts pin_error
end
end
def withdraw(pin_number,amount)
if pin_number == #pin
#balance -= amount
puts "Withdrew #{amount}."
else
puts pin_error
end
end
def deposit(pin_number,amount)
if pin_number ==#pin
#balance+=amount
puts"Deposited"
else
puts pin_error
end
end
private
def pin
#pin = 1
end
def pin_error
return "Access denied: incorrect PIN."
end
end
checking_account=Account.new("bob",200)
checking_account.deposit(1,20)
When i try deposit i get an error on pin, but when i remove the # in the pin checks and treat it as a normal variable it works. In codeacademy it shows that the correct way to check the pin is with
if pin_number==#pin
yet it doesn't work, even though it should, why is that?

pin_number==#pin will return false, because you never set #pin - so it will still be nil.
Instead of using a private method, you could do something like this:
def initialize(name, pin, balance=100)
#name = name
#pin = pin
#balance = balance
end
# ...
checking_account=Account.new("bob", 1, 200)
checking_account.deposit(1, 20)

yet it doesn't work, even though it should, why is that?
Because your #pin is never initialized. Something must be different between your code and what they have at codecademy (they likely initialize #pin in the initializer, which you do not).
but when i remove the # in the pin checks and treat it as a normal variable
Wrong.. This is not a variable. When you make it pin instead of #pin, it starts pointing to that private method of yours. Which returns a pin number. That's why it works.
Note that your code will work as-is, if you simply display_balance before making a deposit. This is called "call order dependency" and it's bad.

Related

Array Method that only outputs the name without instances

When I run the command I get
all the song names and then the instances following it. How do I only
get the names?
class Song
##all = []
attr_accessor :name
def initialize(name)
#name = name
##all << self
end
def self.all
##all
end
def self.print_all_song_names
##all.each do |song|
puts song.name
end
end
end
hotline_bling = Song.new("Hotline Bling")
thriller = Song.new("Thriller")
ninety_nine_problems = Song.new("99 Problems")
thriller = Song.new("Thriller")
puts Song.print_all_song_names
Which outputs:
Hotline Bling
Thriller
99 Problems
Thriller
#<Song:0x00000000058ced30>
#<Song:0x00000000058cecb8>
#<Song:0x00000000058cebc8>
#<Song:0x00000000058ceb50>
The issue with your code is your calling puts here and there.
The code already calls puts in print_all_song_names, and after you call puts Song.print_all_song_names which roughly means call the method and print the value returned.
each returns a receiver, which means print_all_song_names returns the value of ##all class variable. Which gets printed again.
To fix it, just don’t call puts in the last line; Song.print_all_song_names already prints out everything needed.
Song.print_all_song_names # voilà

How i can solve this issue ? undefined method for class

Have a problem, when run a code allways have error.
Expect: for the user add win or reduce his balance.
undefined method `balance=' for #<Dice:0x0000563d4d4dfd88 #name="foo", #balance=600, #bet=300>
Did you mean? balance
(repl):22:in `increase_decrease_cash'
(repl):62:in `<class:Game>'
(repl):29:in `<main>'
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
class Dice
attr_accessor :name, :bet
attr_reader :balance
def initialize(name, balance, bet)
#name = name
#balance = balance
#bet = bet
end
def self.roll
#roll_dice = rand(1..2)
end
def self.check_bet
if #player.bet > #player.balance
puts "Enter number from 1 to #{#player.balance}"
end
end
def self.increase_decrease_cash
if #roll == #my_number
#player.balance += #player.bet
else
#player.balance -= #player.bet
end
end
end
class Game < Dice
#player = Dice.new("foo", 600, 0)
puts "Hello #{#player.name} your balance is: #{#player.balance}"
puts "Bones throwing count times"
a = 2 #gets.chomp.to_i
while a > 0 do
puts ""
puts "Enter your bet !!!"
# PLAYER BET
#player.bet = 300 #gets.chomp.to_i
check_bet
puts "Respected #{#player.name} your bet is: #{#player.bet}"
puts "Now select number 1-2"
# BONES ROLL
#my_number = roll # gets.chomp.to_i
puts "###################"
puts "Now we throw bones"
#roll = roll
puts "Nuber is #{roll}"
if #roll == #my_number
puts "Your win, you get #{#player.bet}"
else
puts "You lose #{#player.bet}"
end
p "$$$$"
p #player.balance
p "$$$$"
a -= 1
increase_decrease_cash
end
end
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
attr_reader creates only the get method for balance. You need both get and set method for balance. Because you set balance in the initialize method. So, you should use attr_accessor instead of attr_reader.
attr_accessor :balance

Is it possible to get rid of class variable?

I started with that question
My goal is to try an unusual, but interesting way of calling a block. I came to that code:
class Chips
def self.configure player_class, player_id_method, player_api_method
player_class.send :define_method, player_api_method do
Chips::Player.new(self.send player_id_method)
end
end
def self.fix_game game_id
##game_results = {game_id: game_id, players: []}
yield
puts ##game_results.inspect
end
class Player
def initialize player_id
#player_id= player_id
end
def gain chips
Chips.send :fix_player, #player_id, chips.abs
end
def lose chips
Chips.send :fix_player, #player_id, -chips.abs
end
end
def self.fix_player player_id, chips
##game_results[:players]<< {player_id: player_id, chips: chips}
end
private_class_method :fix_player
end
class User
attr_reader :email
def initialize email
#email = email
end
end
Chips.configure User, :email, :chips
p1= User.new 'david#gmail.com'
p2= User.new 'simon#gmail.com'
# p1.chips and p2.chips should have access to the value
# currently accessible by ##game_results reference
Chips.fix_game 3333 do
p1.chips.gain 300
p2.chips.lose 300
end
... which outputs {:game_id=>3333, :players=>[{:player_id=>"david#gmail.com", :chips=>300}, {:player_id=>"simon#gmail.com", :chips=>-300}]}
I would like to avoid class variable using.
ps
Question creation form hadn't allow me to submit this question until I added more words, which are in fact a pray to The Mighty Doorkeeper. I've brought you more bytes, TMD. I obey.

math with instance variables

I have this class:
class Account
attr_accessor :balance
def initialize(balance)
#balance = balance
end
def credit(amount)
#balance += amount
end
def debit(amount)
#balance -= amount
end
end
Then, for example, later in the program:
bank_account = Account.new(200)
bank_account.debit(100)
If I call the debit method with the "-=" operator in it (as shown in the class above) the program fails with the following message:
bank2.rb:14:in `debit': undefined method `-' for "200":String (NoMethodError)
from bank2.rb:52:in `<main>'
But if I remove the minus sign and just make it #balance = amount, then it works. Obviously I want it to subtract, but I can't figure out why it doesn't work. Can math not be done with instance variables?
Your value passed into initialize() is a string, rather than an integer. Cast it to an int via .to_i.
def initialize(balance)
# Cast the parameter to an integer, no matter what it receives
# and the other operators will be available to it later
#balance = balance.to_i
end
Likewise, if the parameter passed to debit() and credit() is a string, cast it to an int.
def credit(amount)
#balance += amount.to_i
end
def debit(amount)
#balance -= amount.to_i
end
Finally, I'll add that if you plan to set #balance outside the initialize() method, it is recommended to define its setter to call .to_i implicitly.
def balance=(balance)
#balance = balance.to_i
end
Note: This assumes you want and only intend to use integer values. Use .to_f if you need floating point values.
Most likely, you did
bank_account = Account.new("200")
You should actually do
bank_account = Account.new(200)
try with
def credit(amount)
#balance += amount.to_i
end
def debit(amount)
#balance -= amount.to_i
end
or pass a number as the parameter (the error says that you are passing a string)

Use a method from one class within method from another class?

I've created this game thing in order to learn OOP and I'm having trouble with part of it. Here's what's causing me problems:
I have two classes. On line 3 of class Player, I have some code which is probably way wrong, but basically, what I'm trying to do is use armor to modify how much damage a player receives. I'm getting an error, though: "undefined method 'protection' for nil:NilClass (NoMethodError)
I have Armor as another class. I think the problem might relate to the fact that I am calling #armor.protection when protection is mentioned in Armor and #armor is mentioned in Player, but I am unsure how to fix this. I have added all the code I believe is relevant to my question below. Like I said, I'm really new at this, so please use terminology a noob could understand.
class Player
def equip(armor)
#armor = armor
end
def hit(damage)
#damage = damage - #armor.protection
#health -= damage
end
end
class Armor
def initialize(name, protection)
#protection = protection
end
end
EDIT: added additional code to show all of what I've got going on for clarification. I don't expect anyone to read all of what I've got, though. :S It's probabably scary and snarled up. :P
class Player
def initialize(name, health)
#name = name
#health = health
end
def equip(armor)
#armor = armor
end
def health
#health
end
def health=(value)
#health = value
end
def hit(damage)
damage = damage - #armor.protection
#health -= damage
end
def dead?
if #health <= 0
return true
elsif #health > 0
return false
end
end
def name
#name
end
def attack(target)
damage = rand(30)
puts "#{#name} attacks #{target.name}"
target.hit(damage)
puts "#{#name} hits #{target.name} for #{damage} damage."
end
end
class Armor
def initialize(name, protection)
#protection = protection
end
end
player1 = Player.new("Melanie", 100)
player2 = Player.new("a Monster", 200)
shirt = Armor.new('shirt', 4)
player1.equip(shirt)
while player1.dead? == false && player2.dead? == false
player1.attack(player2)
if player2.health > 0
puts "#{player2.name}'s health is at #{player2.health}."
elsif player2.health <= 0
puts "#{player2.name} has no health."
end
player2.attack(player1)
if player1.health > 0
puts "#{player1.name}'s health is at #{player1.health}."
elsif player1.health <= 0
puts "#{player1.name} has no health."
end
end
if player1.health > player2.health
puts "#{player2.name} is dead."
puts "#{player1.name} wins."
elsif player2.health > player1.health
puts "#{player1.name} is dead."
puts "#{player2.name} wins."
elsif player2.health == player1.health
puts "#{player1.name} and #{player2.name} killed each other."
end
If your Armor class has a protection method, it would work fine. However it doesn't, so even if you were to call it from inside the Armor class, you'd get the same error. To define it you can either use attr_reader or attr_accessor or define it by hand.
class Armor
attr_accessor :protection
def initialize(name, protection)
#protection = protection
end
end
or
class Armor
def initialize(name, protection)
#protection = protection
end
def protection
#protection
end
end
I just ran your 2nd (full) example.
Besides the accessor problem explained in the other answers (just add attr_reader :protection to class Armor), you overlooked something in the test scenario :)
The error message gives a hint: undefined method 'protection' for nil:NilClass (NoMethodError). Given that this is caused in the 1st line of the hit method, this means #armor was nil, and of course nil is not an instance of Armor, so it has no protection method. Why was it nil? Well, look at how your fight begins:
player1 = Player.new("Melanie", 100)
player2 = Player.new("a Monster", 200)
shirt = Armor.new('shirt', 4)
player1.equip(shirt)
Only Melanie has a shirt, and you gave the monster no armor at all! Not very fair, is it :)
To fix that, you either need to give him some armor, or change your hit method so that it still works when #armor was not initialized. A nice OO way of doing that is to initialize all players with a default dummy armor that provides no protection:
class Player
def initialize(name, health)
#armor = Armor.new('nothing', 0)
# ...
Done!
Now, since that dummy armor will be useful whatever the specific rules of your game are, I'd abstract it from the point of view of class Player, by making class Armor responsible for creating it instead:
class Armor
class << self # define a class method, like new
def none
self.new('nothing', 0)
end
end
# ...
Then you can just say Armor.none instead of Armor.new('nothing', 0) in Player.initialize. This way, if you need to change how Armor works internally, you can update the dummy armor at the same time, and without touching other classes.
The problem here is that you have a #protection instance variable, but no "accessor" to get to it. Instance variables are private to the instance of the class they're owned by, so if you want to expose them to the outside world, you have to set up accessors to do so. In this case, you want:
class Armor
attr_reader :protection
...
end
This will let you call #armor.protection, and will return the value of your armor instance's #protection variable.
Try this:
player = Player.new
armor = Armor.new('Mythril', 100)
player = player.equip(armor) #Initialise the armor object inside Player.
player.hit(10)

Resources