#---Class---

A class in Ruby is a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that the objects created from it will have.

Basic Syntax

A class is defined using the class keyword, and the name is a constant that starts with an uppercase.

class Person
end

Use the new method to create an instance of the class:

person = Person.new
p person.class # => Person

While you can define multiple classes in a single file, it’s a good practice to define one class per file.

An empty class isn’t of much use. Let’s add some bells and whistles to it.

Instance Methods

Instance methods are functions that belong to the objects of a class. These are defined inside the class:

class Person
  def greet(msg)
    "Good to see you, #{msg}!"
  end
end
joe = Person.new
p joe.greet('Ron') # => "Good to see you, Ron!"
moe = Person.new
p moe.greet('Dan') # => "Good to see you, Dan!"

Instance Variables

A class is useful only if it can create objects that hold data. Specifically data that uniquely belong to that object. Let’s add some instance variables to our class:

class Person
  def initialize(name, age)
    @name = name
    @age = age
    say_hello
  end
  def say_hello
    p "Hello, I'm #{@name}!"
  end
  def info
    "My name: #{@name}. My age: #{@age}"
  end
end
joe = Person.new('Joe', 30)
p joe.info # => "My name: Joe. My age: 30"
moe = Person.new('Moe', 35)
p moe.info # => "My name: Moe. My age: 35"

The initialize method is a special method that you’ll only define but not call. But it’ll be called internally when you create a new instance of the class using new. It’s used to set up the initial state of the object by assigning default values to instance variables, and also call other methods if needed.

@name and @age here are instance variables. They’re prefixed with @ and are declared and used inside the initialize, or any other instance method of the class.
The data they hold is specific to each instance of the class.

Getters and Setters

Instance variables are private by default. You can’t access them directly from outside the class. To read or write their values, you need to define getter and setter methods.

class Person
  def initialize(name, age)
    @name = name
    @age = age
  end
  def name # @name's getter
    @name
  end
  def name=(new_name) # @name's setter
    @name = new_name
  end
  def age
    @age
  end
  def age=(new_age)
    @age = new_age
  end
  def info
    "My name: #{@name}. My age: #{@age}"
  end
end
joe = Person.new('Joe', 30)
p joe.info # => "My name: Joe. My age: 30"
joe.name = 'Joseph'
joe.age = 31
p joe.info # => "My name: Joseph. My age: 31"

Shorthand Getters and Setters

Defining these manually is tedious. Ruby provides a shorthand way to define getters and setters:

The attr_accessor method automatically creates the instance variables, and also defines both the getter and setter methods for those variables.

class Dude
  attr_accessor :name, :age
  def info
    "My name: #{@name}. My age: #{@age}"
  end
end
joe = Dude.new
joe.name = 'Joseph'
p joe.name # => "Joseph"
joe.age = 31
p joe.info # => "My name: Joseph. My age: 31"

There are also attr_reader and attr_writer shorthands that only create the getter and setter methods respectively.

Class Methods

Class methods belong to the class itself, not to the instances of the class. These too are defined inside the class, but the syntax is slightly different.
There are 3 ways to define a class method:

  • Using the self keyword before the method name:
class Person
  ## A 'factory' method used to create a new person.
  def self.create_new_person(name, age)
    Person.new(name, age)
  end
end

They’re called on the class itself, not on an instance:

d = Person.create_new_person('John', 25)
d.name = 'John'
p d.name # => "John"
  • Using the class << self syntax:
class Person
  class << self
    def population
      ## shortcut for:
      ## @count = @count || 0
      @count ||= 0
    end
    ## A counter to track number of persons.
    def increment
      @count += 1
    end
  end
  def initialize(name, age)
    @name = name
    @age = age
    self.class.increment # call the class method
  end
end
p Person.population # => 0
3.times { |i| Person.new "Joe#{i}", 23 + i }
p Person.population # => 3

Note that here, @count is not an instance variable. It’s a class instance variable because it’s defined inside a class method. It belongs to the class and is shared by all instances of the class.

  • Defining a class method outside the class body:
def Person.answer
  42
end
p Person.answer # => 42
  • There’s another way to define a class method outside the class body. But I’d consider this a variation of the second method above:
class << Person
  def deep_answer
    42
  end
end
p Person.deep_answer # => 42

Private Methods

The methods we’ve defined so far are all public. This means the objects can access them. They’re allowed to access them. These methods act as the api for the object.
But there are times you want to define methods that are only for internal use, and you don’t want the objects to access them. These are the private methods.

Methods defined after the private keyword inside a class definition are all private:

class Person
  def greet(msg)
    log(msg)
    "Good to see you, #{msg}!"
  end
  private
  def log(msg)
    puts "Log: #{msg}"
  end
end
## Without rescue nil, this fails with NoMethodError
## with the error message
## "private method `log' called for #\<Person:0x...\>":
p Person.new('D', 2).log('Hello') rescue nil

send and public_send

For all this talk about not allowing access to private methods, you can still call them via the send method! Defined in the top-level Object class, it allows you to call any method on an object, public or private.
Naughty people use this ‘loophole’ to call private methods like so (sorta like sql injection. This is method injection):

## Live life dangerously!
p Person.new('D', 2).send(:log, 'Hello') # => "Log: Hello"

But the reason send exists is to allow you to call methods dynamically, i.e. when you don’t know its name, and it comes via a string/symbol variable.

To prevent misuse as above, there’s a safer public_send. You can only call public methods using it:

p Person.new('D', 2).public_send(:greet, 'WELCOME')
## Try calling `log` using public_send.

Open Classes aka Monkey-Patching

Did you note that in this whole example, I wrote methods to the Person class one at a time? Each time, I put the methods between the ‘class Person’ and ‘end’ thingies, as if I was re-declaring the class from scratch. But when I accessed its instance_methods and singleton_methods, I got all the methods.

That’s because Ruby classes (and modules) can be opened. This allows you to add new functionality to existing classes without modifying their original definition which might be in a different file, or in a library/gem that you don’t want to modify.

This additive nature is only if we add new methods to the class. If you open a class and add a method that already exists, and you write a different implementation, then the method is redefined. This is called monkey-patching:

class Person
  def self.deep_answer
    43 # We changed the answer! We sure this is correct?
  end
end

Open classes and Monkey-patching are powerful Ruby features that allow you to write idiomatic Ruby code. But they can also lead to unexpected behavior if not used carefully. For example, if you redefine a method in a core class that suits your usecase, it might affect code elsewhere in you application. So use them carefully:

class Array
  def size
    'google' # oops!
  end
end
p [1, 2, 3].size # => "google"

Instrospecting a Class

Since Ruby is dynamic, debugging can be a bit of pita. You can’t expect to use traditional debugging tools like in statically typed languages. Hence why Ruby provides these abilities so you can better debug your code.

You won’t be using these methods in your application code mostly. You’ll likely use them in a REPL like irb or pry.

You can get a list of the various methods and variables defined in a class using the following methods.

  • instance_methods returns a list of methods you can call on objects of the class. Without false, it returns a list of all instance methods, including those inherited from the parent class. It’s a huge list:
p Person.instance_methods # run the code to see full list.
p Person.instance_methods(false)
## [:name=, :name, :say_hello, :info, :age,:age=,:greet]
  • singleton_methods returns a list of class methods defined in the class:
p Person.singleton_methods(false)
## [:create_new_person, :population, :increment, :answer,
## :deep_answer]
  • source_location returns the file and line number where a method is defined:
p Person.method(:increment).source_location
p Person.new('', 1).method(:age).source_location
  • Use const_source_location to find out where a class or a module or any constant in general is defined:
p Module.const_source_location(:Person)
  • instance_variables does what you think it does:
## Calling it on the class returns the class instance
## variables:
p Person.instance_variables # => [:@count]
p Person.new('', 1).instance_variables # => [:@name,:@age]
  • To get the value of an instance variable, use instance_variable_get:
p Person.instance_variable_get(:@count) # => 5
p Person.new('', 1).instance_variable_get(:@age) # => 1
  • As you might expect, there’s also an instance_variable_set that does its thing even if you didn’t define a writer to the instance var:
p Person.instance_variable_set(:@count, 777)
p Person.instance_variable_get(:@count) # => 777
p Person.new('', 1).instance_variable_set(:@age, 99)
  • Use instance_of? to check if an object is an instance of a specific class:
p Person.new('', 1).instance_of?(Person) # => true

You can also use is_a? and kind_of? here for the same purpose. But they’re more used in the context of inheritance.

Some Oddities

  • Class methods can be accessed using the :: operator. This style isn’t common, but it’s valid:
p Person::population
p Person::new('Alice', 28).info
  • When calling any method on any object, there can be spaces or newline around the dot:
p Person . population
r = Person
  . answer
p r # => 42

Notes

  • Instance variables can be defined in any instance method, not just in initialize. They ‘spring into existence’ when you assign a value to them. But it’s a good practice to define them in initialize with some default value.
  • In object oriented paradigm, “calling a method on an object” is called sending a message to the object.

Official Docs