1

I am trying to understand ruby objects and the self keyword.

Lets say I have a class:

class CashRegister
  attr_accessor :total

  def initialize(total)
    @total = total
  end

  def add(amount)
    self.total = self.total + amount
  end
end

cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10)
puts cr.total #=> 15

Say I remove the self keyword in the add method above:

def add(amount)
   total = total + amount
end

I get an error:

cr = CashRegister.new(5)
puts cr.total #=> 5
cr.add(10) #=> this throws an error: ./lib/test.rb:28:in `add': undefined method `+' for nil:NilClass (NoMethodError)

I am assumed this is because I need the self keyword to refer to the instance variable, total

Say I have another similar class:

class School
  attr_accessor :name, :roster

  def initialize(name)
    @name = name
    @roster = {}
  end

  def add_student(student, grade)
    roster[grade] = roster[grade] || []
    roster[grade] << student
  end
end

school = School.new("Jefferson High")
puts school.roster #=> {}
school.add_student("Sam", 10)
puts school.roster #=> {10=>["Sam"]} 

Why did I not need self in add_student?

zippyferguson
  • 57
  • 2
  • 10

1 Answers1

2

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.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Thank you so much for this answer. I made a couple repls of some examples so that I might be able to sort them out. https://repl.it/@FergusDevelopme/cash-register-instance https://repl.it/@FergusDevelopme/school-instance – zippyferguson Jan 12 '20 at 00:40
  • 1
    @zippyferguson Good idea. They *both* write to the local variable because a defined local variable takes precedence over a method call. Once you have `roster = ...` in scope, subsequent references to `roster` will be to the variable, not the method. – Schwern Jan 12 '20 at 02:08