tl;dr: foo = value will always refer to a local variable foo not the method call self.foo=(value).
I am assumed this is because I need the self keyword to refer to the instance variable, total
No, total is a local variable, not an instance variable. @total is an instance variable. A local variable lives for the current scope, like a single method call. An instance variable sticks with the object.
You need the self keyword to refer to the method total=. Let's dive in.
attr_accessor :total declares two methods, total and total=. These are wrappers to get and set the instance variable @total. The following code does the equivalent.
def total
@total
end
def total=(value)
@total = value
end
Note that the method is named total=, this will become important in a moment.
With that in mind, let's look at your code.
def add(amount)
self.total = self.total + amount
end
(Almost) everything in Ruby is really a method call. The above is syntax sugar for calling the total= method on self.
def add(amount)
self.total=(self.total + amount)
end
Now what happens if we remove self like so?
def add(amount)
total = total + amount
end
In Ruby, self is optional. Ruby will figure out if total means the method total or the local variable total. The local variable takes precedence and assignment is always to a local variable.
total = total + amount works like so:
def add(amount)
total = self.total + amount
end
Assignment is always to a local variable.
To further illustrate, what if we declared total first?
def add(amount)
total = 23
self.total = total + amount
end
The existing local variable total takes precedence over the total() method. total + amount refers to the local variable total and so cr.add(10); puts cr.total will be 33.
total = ... will always refer to the local variable total. For this reason if you want to use method assignment you must explicitly use self.total=. In other cases you can drop the self. And avoid local variables with the same name as methods.
def add(amount)
# self.total = self.total + amount
self.total = total + amount
end
Why did I not need self in add_student?
Because there is no ambiguity. You're not assigning to a local variable roster.
def add_student(student, grade)
roster[grade] = roster[grade] || []
roster[grade] << student
end
roster[grade] is really self.roster.[]=(grade). You are calling self.roster which returns a Hash and then calling the []= method on that Hash to give it a new key/value pair.
Similarly, roster[grade] << student is self.roster.[](grade).<<(student). Get aHash, call [[]](https://ruby-doc.org/core/Hash.html#method-i-5B-5D) on it to retrieve theArrayand call [<<](https://ruby-doc.org/core/Array.html#method-i-3C-3C) on thatArray`.
def add_student(student, grade)
self.roster.[]=(grade) = self.roster.[](grade) || []
self.roster.[](grade).<<(student)
end
Yes, [], []=, and << are all methods names like any other.
A lot of Ruby mysteries go away once you understand the method calls under the syntax sugar and that things like [], []=, << and so on are method names.