LearnStreet Ruby for Beginners Lesson 9.12 - ruby

Hi i'm stuck on defining instance methods. The tutorial asks:
Define a method called balance in the class BankAccount which returns the balance.
The code has:
class BankAccount
def initialize(balance)
#balance = balance
end
# your code here
end
I'm really confused as to what the question is asking. Any help would be appreciated...

The tutorial is asking you to define a "getter" for the BankAccount class:
class BankAccount
def initialize(balance)
#balance = balance
end
def balance
#balance # remember that the result of last sentence gets returned in ruby
end
end
Then, you can do
bankAccount = BankAccount.new 22 # the value 22 is the balance that gets passed to initialize
bankAccount.balance # 22
Now, if what the tutorial is asking is a class method (to me it's not really clear), you should do:
def self.balance # self is here so you define the method in the class
#balance
end
Then you can do BankAccount.balance

Ok, let's take the example code:
class BankAccount
def initialize(balance)
#balance = balance
end
# your code here
end
in here you are defining a BankAccount class which define the BankAccount#initialize method (also called constructor) that will be automatically called on the creation of a BankAccount object via BankAccount::new:
BankAccount.new( 123 )
In the above example #balance will be set to 123. #balance is an instance variable (notice the # before the name) which means that you can access it per-object within any method you define.
To return that variable, as the exercise ask, you can use the keyword return within the BankAccount#balance method as follows:
def balance
return #balance
end
The Ruby syntax also allows you to omit return (as it is intended to always return the last evaluated expression from a method) leading to a more concise syntax:
def balance
#balance
end
For this kind of getter-methods (= methods that return an instance variable) there is an easily utility: attr_reader that you can use as follows:
class BankAccount
attr_reader :balance
def initialize(balance)
#balance = balance
end
end
But don't worry, you'll probably learn about the above very soon.
Happy learning.

class BankAccount
attr_reader :balance
def initialize(balance)
#balance = balance
end
end
add attr_reader :balance

Related

Ruby classes pass variables between

Is there any way to pass variables between classes?
I have the next code.
module Test
class Super
def initialize(name)
#name = name
end
end
class Upper
def test
puts #name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()
Thank you!
No, because a is an instance of the class. Two answers for you;
1) It's better programming practice to have send a to b. So you'd do something like this; (assuming attr_reader :name)
class Upper
def test(s)
s.name
end
end
a = Test::Super.new('My Name')
u = Test::Upper.new
u.test(a)
or you could have it part of the setup; I won't give you all the code, but here's how it'd look
a = Test::Super.new('My name')
b = Test::Upper.new(a)
b.test
=> 'My name'
Neither of these examples is particularly good practice for classes but I imagine you have a more specific use case you're trying to achieve that has been anonymised for the purpose of this question :)
If for some reason instances of the class Upper need to have an access to the internals of instances of the class Super, it means you have a design flaw.
One possible way would be Super needs to expose the variable via a getter:
module Test
class Super
def initialize(name)
#name = name
end
def name
#name
end
end
end
Now you might get the name with Test::Super.new("my name").name.
Another possibility is Upper is actually a subclass of Super:
class Upper < Super
def test
puts #name
end
end
Now Test::Upper.new("my name").test will print "my name", because Upper derives the implementation from Super.
Also, one might use an instance variable on the enclosing module level:
module Test
def self.name=(name)
#name = name
end
def self.name
#name
end
class Super
def initialize(name)
Test.name = name
end
end
class Upper
def test
puts Test.name
end
end
end
This would print:
▶ Test::Super.new("my")
#⇒ #<Test::Super:0x0055dae57fe390>
▶ Test::Upper.new.test
#⇒ "my"
You can make use of class variables in module, which is supported natively by Ruby.
You can do:
module Test
##name = ''
class Super
def initialize(name)
##name = name
end
end
class Upper
def test
puts ##name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()

How do I make my class attribute not be able to access instance variable directly?

How can I make my code work so that the balance attribute doesn't access the instance variable(I believe it's #balance) directly? Can someone explain what it means for an attribute to access an instance variable?
I'm new to using Ruby and just got into learning about Ruby classes. In this chapter, my objectives is to understand the concept of instance variables, demonstrate the us of getter and setter methods, understand how to use instance methods, and understand the concept of encapsulation.
class BankAccount
attr_accessor :balance
def initialize(balance)
#balance = balance
end
def withdraw(amount)
if (balance >= amount)
#balance = balance - amount
end
end
end
In Ruby attr_reader :balance is more or less just a convenience version of the following method:
def balance
#balance
end
Similarly, attr_writer :balance is just a short form for
def balance=(value)
#balance = value
end
And attr_accessor :balance is short for attr_reader :balance plus attr_writer :balance.
So as you can see attr_reader accessing the instance variable is nothing special, e.g. in your code you also access the instance variable in #initalize and #withdraw.
You need to clarify why you wouldn't want to access it directly. And what that even means. Because you can either access the instance variable using #balance or not, there is no indirect in my opinion.
Define your own version of balance reader method:
class BankAccount
attr_accessor :balance
def initialize(balance)
#balance = balance
end
def withdraw(amount)
if (balance >= amount)
#balance = balance - amount
end
end
def balance
'balance from method directly'
end
end
Now when you call balance method, it'd read your defined one and return value accordingly.

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

Pre-defining variables

This is my class structure:
class Commodity
def initialize(name)
#name = name
end
class PriceSeries
def initialize(name)
#name = name
end
end
end
I want to instantiate the Commodity class:
gold = Commodity.new("gold")
then instantiate the PricePoint class:
gold.xau = Commodity::PriceSeries.new("gold")
It seems that I can only do this with an attribute accessor called xau in the gold class. Is this the only way to define that variable?
def xau
xau
end
I feel like this shouldn't be a function.
Well, there are a lot of ways to do it, but attr_accessor is by far the simplest:
class Commodity
attr_accessor :xau
end
gold = Commodity.new("gold")
gold.xau = some_value
What attr_accessor :xau does is defines a xau= method that assigns its argument to the instance variable #xau, and another method xau that returns the value of #xau. In other words, it basically does this:
class Commodity
# Setter
def xau=(value)
#xau = value
end
# Getter
def xau
#xau
end
end
For convenience and readability, Ruby lets you put whitespace before the = in gold.xau = foo, but in fact it's the same as the method call gold.xau=(foo).
I'm pretty sure what you want is attr_accessor:
class Commodity
attr_accessor :xau
end
Which is essentially equivalent to this:
class Commodity
def xau
#xau
end
def xau=(value)
#xau = value
end
end
It is not clear what you want to do, but it looks like you want to do this:
class Commodity
def initialize(name)
#name = name
#xau = Commodity::PriceSeries.new(name)
end
end
Then,
gold = Commodity.new("gold")
will automatically define
Commodity::PriceSeries.new("gold")
as the value of an instance variable #xau of gold.

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)

Resources