Menu
Coddy logo textTech

self in Methods

Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 9 of 57.

Inside a method, name = value always creates a local variable, even when the class has a setter name=. To call the setter, write the receiver: self.name = value:

class Player
  attr_accessor :level

  def initialize
    @level = 1
  end

  def broken_level_up
    level = level + 1
  rescue NoMethodError
    puts "level is a new local variable here, still nil"
  end

  def level_up
    self.level = level + 1
  end
end

p1 = Player.new
p1.broken_level_up
p1.level_up
puts p1.level

Output:

level is a new local variable here, still nil
2

Readers need no self.: level and self.level call the same method. The usual style writes self. only where Ruby needs it, in front of a setter:

class Rect
  attr_accessor :w, :h

  def initialize(w, h)
    @w = w
    @h = h
  end

  def area
    w * h
  end

  def double!
    self.w = w * 2
    self.h = self.h * 2
    self
  end
end

puts Rect.new(2, 3).double!.area

Output:

24

Calling the setter from initialize with self. makes new objects pass the same check as later changes, so the rule lives in one place:

class Product
  attr_reader :price

  def initialize(price)
    self.price = price
  end

  def price=(value)
    raise ArgumentError, "price must be positive" if value <= 0

    @price = value
  end
end

puts Product.new(5).price
begin
  Product.new(0)
rescue ArgumentError => e
  puts e.message
end

Output:

5
price must be positive

What self is depends on where the code runs. Inside an instance method it is the instance; in the class body, outside every method, it is the class itself:

class Robot
  puts "in the class body, self is #{self}"

  def whoami
    "in a method, self is a #{self.class}"
  end
end

puts Robot.new.whoami

Output:

in the class body, self is Robot
in a method, self is a Robot
challenge icon

Challenge

Easy

The class Account has a custom setter balance= that raises ArgumentError with balance cannot be negative for a value below 0. Complete initialize(owner, balance), deposit(amount) and withdraw(amount) so every change of the balance goes through that setter with self., including the starting balance.

The supplied code reads the owner and the starting balance, then commands +n (deposit) or -n (withdraw). It prints Ada: 120 after each command, or the error message and the unchanged balance. An invalid starting balance prints the error message only.

Your code goes in account.rb. main.rb holds the supplied input/output code and cannot be edited.

Try it yourself

require_relative 'account'

# Supplied input/output code: keep it as it is
input = []
while (line = gets)
  input << line.chomp
end
begin
  account = Account.new(input[0], input[1].to_i)
  input.drop(2).each do |cmd|
    begin
      amount = cmd[1..].to_i
      cmd.start_with?("+") ? account.deposit(amount) : account.withdraw(amount)
    rescue ArgumentError => e
      puts e.message
    end
    puts "#{account.owner}: #{account.balance}"
  end
rescue ArgumentError => e
  puts e.message
end
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Ruby compiler