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 a
Hash, call [
[]](https://ruby-doc.org/core/Hash.html#method-i-5B-5D) on it to retrieve the
Arrayand call [
<<](https://ruby-doc.org/core/Array.html#method-i-3C-3C) on that
Array`.
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.