Classes

Blueprints for creating objects. They define attributes and methods that objects instantiated from the class will have. Instance variables are prefixed with @. Class variables are prefixed with @@

class Person
 @@species = "homo sapien" # class var
  attr_accessor :name, :age # provides getter and setter methods
 
  def initialize(name, age)
    @name = name # instance var
    @age = age
  end
 
  class << self
    def species # exposes class variable
      @@species
    end
  end
end
 
person = Person.new("Alice", 25)
puts "I am a #{Person.species}"

Methods

Different types of methods include: instance methods, class methods, and module methods.

class Person
  attr_accessor :name
 
  def initialize(name)
    @name = name
  end
 
  # instance methods operate on the instance
  def hi
    "Hi, I'm #{@name}."
  end
 
  class << self # class methods operate on the class itself
  # "class methods" are just singleton methods on the class object
    def info # self refers to the class
      "This is a Person class."
    end
  end
end
 
person = Person.new("Alice")
puts person.hi # Hi, I'm Alice.
puts Person.info # This is a Person class.

Modules (mixins)

A way to organize and namespace our classes

Modules can be mixed into classes using include (as instance method), extend (as class method), or prepend (as instance methods that override local instance methods)

prepend methods technically don’t override local methods, they just appear earlier in the method lookup

module Flyable
  def fly # module method
    "I can fly!"
  end
end
 
class Bird
  include Flyable # module method will operate on instance
end
 
class Plane
  extend Flyable # module method will operate on class
end
 
class Kite
  prepend Flyable
 
  def fly
    "I can fly if there is wind"
  end
 
  class << self
    def fly
      "Kites can fly!"
    end
  end
end
 
bird = Bird.new
kite = Kite.new
 
puts bird.fly       # I can fly!
puts Plane.fly      # I can fly!
puts kite.fly       # I can fly!
puts Kite.fly       # Kites can fly!

Singleton Methods

A method on a single object

ℹ️

The term class method is just shorthand for a Classes singleton method

class Kite; end
kite = Kite.new
 
def kite.crash
  "We are stuck in a tree!"
end
 
puts kite.crash # we are stuck in a tree
puts Kite.crash # NoMethodError
 
kite.class # creator class Kite
kite.ancestors # [Kite, Object, Kernel, BasicObject]
kite.singleton_class.ancestors # its own class [<Class:#<Kite:0x...>>, Kite, Object, Kernel, BasicObject]

class << self opens the singleton class of self

class << Fruit
  def yellow; "banana"; end
end
# is the same as
def Fruit.yellow; "banana"; end

Inheritance

A class can inherit from another using <. super calls the parent method with the same name

class Animal # parent class
  attr_reader :species
 
  def initialize(species)
    @species = species
  end
end
 
class Dog < Animal # child class
  attr_reader :name
 
  def initialize(name)
    super("Dog") # calls Animal's initialize with provided args
    # super() calls with no args
    # super calls with current args (name)
    @name = name
  end
end
 
dog = Dog.new("Buddy")
puts "#{dog.species} #{dog.name}" # Dog Buddy

Polymorphism/Overriding

A subclass can redefine methods from its parent class

class Animal
  def speak
    "Some generic sound"
  end
end
 
class Cat < Animal
  def speak
    "Meow!"
  end
end
 
animals = [Animal.new, Cat.new]
animals.each do |animal|
  puts animal.speak # Some generic sound, Meow!
end

Encapsulation

Methods may be:

  • Public: default, accessible anywhere
  • Private: can only be called within the class
  • Protected: can be accessed by instances of the same class or its subclasses
class Person
  def initialize(name, secret)
    @name = name
    @secret = secret
  end
 
  def introduce
    "Hi, I'm #{@name}."
  end
 
  private # methods below this are private
 
  def reveal_secret
    @secret
  end
 
end
 
person = Person.new("Alice", "I love Ruby.")
puts person.introduce # Hi, I'm Alice.
puts person.reveal_secret  # errors

Reusing Code

Blocks

Chunks of code that can be passed to methods

# implicit block
def greet_with_block(name)
  yield(name) if block_given? # yields to the block, if block is given
end
 
# explicit block
def greet_with_block(name, &block) # &block represents the proc object
  block.call(name) if block
end
 
greet_with_block("Alice") { |name| puts "Hello, #{name}!" } # Hello, Alice!

Procs and Lambdas

Procs are objects that hold blocks of code

add = proc { |a, b| a + b }
puts add.call(2, 3) # 5
puts add.call(2) # TypeError 2 + nil

Proc shorthand (:&)

arr.map(&:to_s) # same as arr.map { |item| item.to_s }

Lambdas are Procs with strict argument checking

add = ->(a, b) { a + b }
puts add.call(2, 3) # 5
puts add.call(2) # errors, wrong number of arguments

Manipulating Built ins

Built in methods can be overridden to customize behavior

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end
 
  def to_s # string representation
    "Name: #{@name}, Age: #{@age}"
  end
 
  def inspect # used for debugging
    "#<Person: #{@name}, #{@age}>"
  end
end
 
person = Person.new("Alice", 30)
puts person.to_s     # Name: Alice, Age: 30
puts person.inspect  # #<Person: Alice, 30>