Ruby: Using instance variables from other classes - ruby

I have the following code:
class Person
attr_reader :name, :balance
def initialize(name, balance=0)
#name = name
#balance = balance
puts "Hi, #{name}. You have $#{balance}!"
end
end
class Bank
attr_reader :bank_name
def initialize(bank_name)
#bank_name = bank_name
puts "#{bank_name} bank was just created."
end
def open_account(name)
puts "#{name}, thanks for opening an account at #{bank_name}!"
end
end
chase = Bank.new("JP Morgan Chase")
wells_fargo = Bank.new("Wells Fargo")
me = Person.new("Shehzan", 500)
friend1 = Person.new("John", 1000)
chase.open_account(me)
chase.open_account(friend1)
wells_fargo.open_account(me)
wells_fargo.open_account(friend1)
When I call chase.open_account(me) I get the result Person:0x000001030854e0, thanks for opening an account at JP Morgan Chase!. I seem to be getting the unique_id (?) and not the name I assigned to #name when I created me = Person.new("Shehzan", 500),. I've read a lot about class / instance variables and just can't seem to figure it out.

This is because you are passing an instance object assigned to the name variable. You have to do:
def open_account(person)
puts "#{person.name}, thanks for opening an account at #{bank_name}!"
end
Or:
wells_fargo.open_account(friend1.name)

Here you are passing an instance of Person, not a string.
chase.open_account(me)
You have to either pass me.name or modify open_account method to call Person#name like this
def open_account(person)
puts "#{person.name}, thanks for opening an account at #{bank_name}!"
end

You passing an object to the open_account method
You need to do
def open_account(person)
puts "#{person.name}, thanks for opening an account at #{bank_name}!"
end

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à

Ruby object initialization using instance_eval

Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">

Trying to figure out what's wrong with this code

I can't come up with a solution.
class Person
def initialize(name)
#name = name
end
def greet(other_name)
#other_name
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
I would appreciate a helping hand.
You have to make a decision:
Do you want to provide both names when initializing the Person:
class Person
def initialize(name, other)
#name = name
#other = other
end
def greet
puts "Hi #{#other}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
#=> Hi Jerry, my name is Tom
Or do you want to provide the second name when calling the greet method:
class Person
def initialize(name)
#name = name
end
def greet(other)
puts "Hi #{other}, my name is #{#name}"
end
end
kit = Person.new("Tom")
kit.greet("Jerry")
#=> Hi Jerry, my name is Tom
In the initialize method, you should take in two parameters, because you are calling it with two. You were declaring the #other_name variable inside the greet function instead of the initialize one.
This will work.
class Person
def initialize(name, other_name)
#name = name
#other_name = other_name
end
def greet
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
https://repl.it/C5wn
Consider writing your code like this:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def greet(person)
puts "Hi #{person.name}, my name is #{name}"
end
end
tom = Person.new("Tom")
jer = Person.new("Jerry")
tom.greet(jer) #=> Hi Jerry, my name is Tom.
This way you actually have another person as an object instead of just a name.

Ruby: issues with an undefined method error

I am working on a ruby banking problem and I keep coming across this error when trying to write the code for the deposit method. I would like the deposit method to put out a puts statement saying this person has enough cash to make this deposit and state amount, or it states they do not have enough cash to deposit. It says this error in my irb:
NoMethodError: undefined method `<' for nil:NilClass
from banking2.rb:30:in `deposit'
from banking2.rb:59:in `<top (required)>'
Can someone please help me find my error? I've tried several options but am not able to figure it out.
class Person
attr_accessor :name, :cash_amount
def initialize(name, cash_amount)
#name = name
#cash_amount = #cash_amount
puts "Hi, #{#name}. You have #{cash_amount}!"
end
end
class Bank
attr_accessor :balance, :bank_name
def initialize(bank_name)
#bank_name = bank_name
#balance = {} #creates a hash that will have the person's account balances
puts "#{bank_name} bank was just created."
end
def open_account(person)
#balance[person.name]=0 #adds the person to the balance hash and gives their balance 0 starting off.
puts "#{person.name}, thanks for opening an account at #{bank_name}."
end
def deposit(person, amount)
#deposit section I can't get to work
if person.cash_amount < amount
puts "You do not have enough funds to deposit this #{amount}."
else
puts "#{person.name} deposited #{amount} to #{bank_name}. #{person.name} has #{person.cash_amount}. #{person.name}'s account has #{#balance}."
end
end
def withdraw(person, amount)
#yet to write this code
# expected sentence puts "#{person.name} withdrew $#{amount} from #{bank_name}. #{person.name} has #{person.cash_amount} cash remaining. #{person.name}'s account has #{#balance}. "
end
def transfer(bank_name)
end
end
chase = Bank.new("JP Morgan Chase")
wells_fargo = Bank.new("Wells Fargo")
person1 = Person.new("Chris", 500)
chase.open_account(person1)
wells_fargo.open_account(person1)
chase.deposit(person1, 200)
chase.withdraw(person1, 1000)
Change this in your initialize method on Person:
#cash_amount = #cash_amount
To this:
#cash_amount = cash_amount
You added an extra # sign, so you set #cash_amount to #cash_amount. The default value for an uninitialized instance variable is nil in Ruby.
The only place you have a < is in person.cash_amount < amount, so the error is coming from there - person.cash_amount is nil.
Look at where cash_amount is being defined in your Person initializer - you are passing in def initialize(name, cash_amount) but then you are calling #cash_amount = #cash_amount!
Remove the second # so you are actually assigning #cash_amount with the value you are passing in in cash_amount.

Checking Variables In The Constructor

I am learning ruby and know that in other languages I can ensure that variables are not empty when initializing.
How would I do this in Ruby with this example?
class Person
attr_reader :name
def initialize name
#name = name
end
end
Without setting a default name, and raising an exception if an empty string is sent (not nil) as the argument, then you could raise an exception.
class Person
attr_reader :name
def initialize name
raise "Name can not be an empty String" if name.empty?
#name = name
end
end
Then if you were to send an empty string you would get an error similar to this:
temp.rb:5:in `initialize': Name can not be an empty String (RuntimeError)
from temp.rb:11:in `new'
from temp.rb:11:in `<main>'
shell returned 1
As far as the part of "It makes them enter another name until valid" as you mentioned in another comment... you should let the running code do this.
This is allowing no default, but forcing the user to use something other than an empty string.
class PersonNameError < Exception
end
class Person
attr_reader :name
def initialize name
#name = name
end
end
begin
print "What is your name? "
name = gets.chomp
raise PersonNameError if name.empty?
person = Person.new(name)
rescue PersonNameError
if name.empty?
puts "You can not have a name that is ''"
retry
else
raise
end
end
puts person.name
It all depends on what you mean by empty. If you mean calling Person.new without the name variable set like:
a = Person.new
then you should define a default value for it:
def initialize( name = 'Jeffrey' )
#name = name
end
If you mean a nil value then you can do:
def initialize name
#name = (name.nil?) ? 'Jeffrey' : name
end
then from here I think you know how to go with empty strings ( #name = (name == '') ? 'Jeffrey' : name ) and so on.
I'm sure people who throw some interesting and much better answers but here's mine.
class Person
attr_reader :name
# By setting name = nil, we are letting initialize
# know that the parameter name may or may not be given.
def initialize(name = nil)
if name
#name = name
else
# This is what you do when the user
# did not add a name in the initializer.
#name = "No name"
end
end
end
So if you do something like
person = Person.new
the person will have the name No name.
However, if you do
person = Person.new("Bob")
then person will have the name Bob.
Finally if you set a variable as nil, for example
name = nil
then pass the name into the new method
Person.new(name)
then the person will have the name No name.
Based on what the other answers are doing, the best way IMO is:
def initialize(name = 'Default name')
#name = name
end
After your comment...
Is this Rails? That would change a few things. But you can just nil check the value:
def initialize(name)
if name.nil? || name.empty?
# raise an exception
else
#name = name
end
end
If you're using Rails, you can also just use blank? which will check both nil? and empty?.
Why not accept the user name from within the constructor / initialize method?
def initialize name
begin
while name.empty?
puts "You can not have a name that is ''"
print "What is your name? "
name = gets.chomp
end
#name = name
end
end

Resources