Skip to main content
View
Chapter 8 / 16Classes and objects
1. Variables and data types
2. Strings and symbols
3. Arrays
4. Hashes
5. Conditionals
6. Loops and iterators
7. Methods and blocks
8. Classes and objects
9. Inheritance
10. Modules
11. Extending code with prepend
12. Enumerable
13. Where to go next
14. Error handling
15. Reading and writing files
16. Advanced class features

Classes and objects

This chapter introduces classes, the central mechanism for organizing code in Ruby. A class groups data and behaviors in a single place.

Principle

Up until now, we used Hashes to represent a Pokemon: { name: 'Pikachu', level: 25 }. It works, but it has limitations:

  • You cannot control the values (nothing prevents setting a negative level)
  • You cannot attach behavior to the Hash (no heal or take_damage method)
  • You can make typos in the keys without Ruby warning you

A class solves all of this. It is a blueprint that defines:

  • What data the object contains (instance variables @)
  • What behaviors it has (methods)

An object is an instance created from this blueprint.

Defining a class

class Pokemon
def initialize(name, level)
@name = name
@level = level
end
end

pikachu = Pokemon.new('Pikachu', 25)
p pikachu # => #<Pokemon @name="Pikachu", @level=25>
  • class Pokemon ... end defines the class. The name starts with a capital letter (CamelCase convention).
  • initialize is a special method called automatically by .new. It is the constructor: it receives the arguments and initializes the instance variables.
  • @name and @level are instance variables. They belong to the object and persist between method calls.
  • Pokemon.new('Pikachu', 25) creates a new object and calls initialize with these arguments.

Instance variables and accessors

Instance variables (@name, @level) are private by default. You cannot access them from outside:

pikachu = Pokemon.new('Pikachu', 25)
# pikachu.name # => Error! NoMethodError

To allow reading, use attr_reader:

class Pokemon
attr_reader :name, :level

def initialize(name, level)
@name = name
@level = level
end
end

pikachu = Pokemon.new('Pikachu', 25)
puts pikachu.name # => Pikachu
puts pikachu.level # => 25
# pikachu.level = 50 # => Error! No setter
  • attr_reader :name, :level creates reader methods (getters) for @name and @level.

There are also:

  • attr_writer :level: creates a setter only (pikachu.level = 50)
  • attr_accessor :level: creates getter and setter

But attr_accessor does not perform any validation. If you want to control the values, you write a custom setter.

Custom setters

A custom setter is a method whose name ends with =. Ruby translates pikachu.level = 50 into a call to this method:

class Pokemon
attr_reader :name, :level

def initialize(name, level)
@name = name
self.level = level
end

# Custom setter: level is always between 1 and 100
def level=(new_level)
@level = new_level.clamp(1, 100)
return @level
end
end

pokemon = Pokemon.new('Charizard', 150)
puts pokemon.level # => 100 (clamped by the setter)

pokemon.level = -5
puts pokemon.level # => 1 (clamped by the setter)
  • def level=(new_level): the = is part of the method name. This is what allows writing pokemon.level = 50.
  • .clamp(1, 100) bounds the value between 1 and 100.
  • self.level = level in initialize calls the setter (and therefore the validation). If we wrote @level = level, we would bypass the validation.

Instance methods

Methods defined inside a class are instance methods. They operate on the object's data:

class Pokemon
attr_reader :name, :hp, :max_hp

def initialize(name, max_hp)
@name = name
@max_hp = max_hp
@hp = max_hp
end

def take_damage(amount)
@hp = (@hp - amount).clamp(0, @max_hp)
return nil
end

def heal
@hp = @max_hp
return nil
end

def fainted?
return @hp <= 0
end
end

pikachu = Pokemon.new('Pikachu', 55)
pikachu.take_damage(30)
puts pikachu.hp # => 25
puts pikachu.fainted? # => false

pikachu.take_damage(50)
puts pikachu.hp # => 0 (does not go below 0)
puts pikachu.fainted? # => true

pikachu.heal
puts pikachu.hp # => 55
  • Each method accesses the instance variables of the object it is called on.
  • fainted? ends with ?: a Ruby convention for methods that return a boolean.

self -- the current object

self refers to the current object. Inside an instance method, self is the object on which the method was called:

class Pokemon
attr_reader :name

def initialize(name)
@name = name
end

def introduce
puts "I am #{self.name}!"
# Equivalent to:
puts "I am #{name}!"
# Equivalent to:
puts "I am #{@name}!"
end
end
  • self.name, name, and @name all access the same data here. self. is optional for getters.
  • self is required to call a setter: self.level = 50. Without self., Ruby would think you are creating a local variable.

Class methods

A class method is called on the class itself, not on an object. You define it with def self.:

class Pokemon
attr_reader :name, :level

def initialize(name, level)
@name = name
@level = level
end

# Class method: creates a starter at level 5
def self.starter(name)
return Pokemon.new(name, 5)
end
end

pikachu = Pokemon.starter('Pikachu')
puts pikachu.level # => 5
  • def self.starter is called via Pokemon.starter(...), not via an object.
  • This is a factory pattern: a method that creates objects with a predefined configuration.

To group multiple class methods, you can use class << self:

class Pokemon
class << self
def starter(name)
return Pokemon.new(name, 5)
end

def from_hash(data)
return Pokemon.new(data[:name], data[:level])
end
end
end

Visibility: public, private, protected

By default, all methods are public. You can restrict access:

class Pokemon
attr_reader :name

def initialize(name, level)
@name = name
@level = level
end

# Public: accessible from outside
def summary
puts "#{@name} Lvl.#{@level}"
puts " PV max : #{calculate_max_hp}"
end

private

# Private: accessible only from inside the class
def calculate_max_hp
return @level * 3 + 10
end
end

pikachu = Pokemon.new('Pikachu', 25)
pikachu.summary # Works
# pikachu.calculate_max_hp # => Error! NoMethodError
  • private: everything below this keyword becomes private. These methods are only accessible from inside the class.
  • protected: like private, but methods are accessible between instances of the same class. Useful for comparisons between objects.

to_s -- readable display

to_s is a special method: Ruby calls it automatically when you use puts or string interpolation #{}:

class Pokemon
def initialize(name, level)
@name = name
@level = level
end

def to_s
return "#{@name} Lvl.#{@level}"
end
end

pikachu = Pokemon.new('Pikachu', 25)
puts pikachu # => Pikachu Lvl.25
puts "Go, #{pikachu} !" # => Go, Pikachu Lvl.25 !
  • Without to_s, puts pikachu would display something like #<Pokemon:0x00007f...>, which is not very useful.

Constants inside a class

class Pokemon
MAX_LEVEL = 100

def initialize(name, level)
@name = name
@level = level.clamp(1, MAX_LEVEL)
end
end

puts Pokemon::MAX_LEVEL # => 100
  • Constants defined inside a class are accessible with :: from outside.
  • Inside the class, you access them directly by name.

Conclusion

  • class ClassName ... end defines a class. .new creates an object and calls initialize.
  • Instance variables (@var) store the object's data. They are private by default.
  • attr_reader creates getters. attr_accessor creates getters + setters (without validation).
  • Custom setters (def level=(value)) allow validating the data.
  • self is the current object. Required to call a setter (self.level = 50).
  • def self.method_name defines a class method (called on the class, not on an object).
  • private makes methods accessible only from inside the class.
  • to_s defines the readable display of an object for puts and interpolation.