#---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.
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 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!"
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.
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"
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 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:
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"
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.
def Person.answer
42
end
p Person.answer # => 42
class << Person
def deep_answer
42
end
end
p Person.deep_answer # => 42
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
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.
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"
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
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]
instance_variable_get
:p Person.instance_variable_get(:@count) # => 5
p Person.new('', 1).instance_variable_get(:@age) # => 1
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)
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.
::
operator.
This style isn’t common, but it’s valid:p Person::population
p Person::new('Alice', 28).info
p Person . population
r = Person
. answer
p r # => 42
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.