let's review the main concepts in object oriented programming (OOP) in Ruby 📖 💎
- In Ruby, everything is an object! anything that can be said to have a value is an object , that includes numbers, strings, arrays, and even classes and modules
'hi'.class # => String
1.class #=> Integer
Create class
- class is a template of objects
- a class definition is a region of code between the keywords
class
andend
- class name : CamelCase naming convention as class/module name must be CONSTANT
class Employee
# class code live here
end
Create objects (instantiation)
- use
new
class method to create object
employee = Employee.new
initialize
method
- When you call
new
to create a new object, Ruby calls that object'sinitialize
method as one step in create the object - any parameter passed to
new
will pass toinitialize
- this will help us to execute and code that we need in create object like set the state or data of these objects
class Employee
def initialize
puts "This object was initialized!"
end
end
employee = Employee.new #=> This object was initialized!
Work with Instances
Instance Variables
- store data of instance (something instance can have) as each instance has it is own version of this variable
- it has the
@
symbol in front of it - It is a variable that exists as long as the object instance exists
- From outside the object, instance variables cannot be altered or even observed (i.e., ruby's instance variables are never public).
- As with globals, instance variables have the
nil
value until they are initialized.
class Employee
def initialize(name)
@name = name
end
end
employee1 = Employee.new("Sara")
employee2 = Employee.new("Khalid")
Instance Methods
- define behavior of instance (something instance can do)
- methods that objects can call
class Employee
def initialize(name)
@name = name
end
def say_hi
"Hi, I am #{@name}"
end
end
employee1 = Employee.new("Sara")
employee2 = Employee.new("Khalid")
employee1.say_hi # => "Hi, I am Sara"
employee2.say_hi # => "Hi, I am Khalid"
Accessor Methods
- in the above example we can not access instance variable
name
outside the class.
employee1.name # => NoMethodError (undefined method `name' for #<Employee:0x0000561c18d842c8 @name="Sara">)
-
NoMethodError
means that we called a method that doesn't exist or is unavailable to the object. - If we want to access the instance variable, we have to create a method that will deal with it.
- getter method : method return the value (get)
-
setter method : method change the value (set), Ruby syntactical sugar to use
def method_name=()
that will help us to use assignment to call the setter method. - as a convention, Rubyists typically name those getter and setter methods using the same name as the instance variable.
class Employee
def initialize(name)
@name = name
end
def name
@name
end
# normal method to set the value
def set_name(new_name)
@name = new_name
end
# syntactical sugar
def name=(new_name)
@name = new_name
end
end
employee1 = Employee.new("Sara")
employee2 = Employee.new("Khalid")
puts employee1.name # Sara
puts employee2.name # Khalid
employee1.set_name("new name")
puts employee1.name
employee2.name=("Omer")
puts employee2.name # Omer
employee2.name = "Ali"
puts employee2.name # Ali
- Syntactical sugar setter methods always return the value that is passed in as an argument and ignore the return value of method, because We defined the value of the assignment as the value of the right hand expression, not the return value from the assigning method
class Employee
def initialize(name)
@name = name
end
def name
@name
end
def name=(new_name)
@name = new_name
"Hi Sara this value will ignored"
end
end
employee1 = Employee.new("Sara")
# call the method as assignments will not return the value from the method
# all other assignments evaluate to the right-hand side in Ruby
employee1.name = "Ali" # => "Ali"
# call the method with send will return the last value
employee1.public_send(:name=, "Ali") # => "Hi Sara this value will ignored"
attr_accessor , attr_reader ,attr_writer
- help us define more getters and setters
-
attr_reader
: getter method only allows you to retrieve the instance variable -
attr_writer
: setter method only allows you to set the instance variable -
attr_accessor
: method takes a symbol as an argument, which it uses to create the method name for the getter and setter methods
class Employee
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
employee = Employee.new("Sara", 20)
employee.name = "Omer"
puts employee.name # Omer
employee.age = 15
puts employee.age # 15
- removing the
@
symbol, we're now calling the instance method, rather than the instance variable as it's generally a good idea because by this we can limit the access to these variables to certain conditions or logic
Work with classes
Class Methods
- Class level methods, Class methods are methods we can call directly on the class itself, without having to instantiate any objects
- defining a class method, we prepend the method name with the reserved word
self
or class name - use
self
is better as you do not need to change the methods name if you will change the class name
class Employee
def Employee.what_am_i?
"I am #{self} class"
end
def self.call
"#{self} is calling: " + what_am_i?
end
end
puts Employee.what_am_i? # I am Employee class
puts Employee.call # Employee is calling: I am Employee class
Class Variables
- Class level data shared between instances of classes
- Class variables are created using two @ symbols like so:
@@
- same as instance variable we can not access class variable from outside the class
class Employee
@@number_of_employees = 0
def initialize
@@number_of_employees += 1
puts "Employee number #{@@number_of_employees} created successfully"
end
end
Employee.new # Employee number 1 created successfully
Employee.new # Employee number 2 created successfully
puts Employee.number_of_employees # => NoMethodError (undefined method `number_of_employees' for Employee:Class)
Class reader / writer methods
- same as instance reader and writer (getter and setter ) methods , just use
self
- no
attr_accessor
,attr_reader
andattr_writer
for class variables in ruby
class Employee
@@number_of_employees = 0
def initialize
@@number_of_employees += 1
puts "Employee number #{@@number_of_employees} created successfully"
end
def self.number_of_employees=(number)
@@number_of_employees = number
end
def self.number_of_employees
@@number_of_employees
end
end
puts Employee.number_of_employees # => 0
Employee.new # Employee number 1 created successfully
Employee.new #Employee number 2 created successfully
puts Employee.number_of_employees # => 2
puts Employee.number_of_employees = 0
puts Employee.number_of_employees # => 0
NOTE: you can define class level instance method using singleton class and class instance variables (Advanced you can read about it)
Understanding self
self
which refers to the currently executing object, and changes depending on the scope it is used in
To make things clear, from within a class :
1- self
, inside of an instance method, references the instance (object) that called the method - the calling object. Therefore, self.name=
is the same as employee1.name=
, in our example.
2- self
, outside of an instance method, references the class and can be used to define class methods. Therefore if we define a class method, def self.number_of_employees=(n)
is the same as def Employee.number_of_employees=(n)
.
class Employee
puts "self is #{self}"
def say
puts "self is #{self}"
puts "self class is #{self.class}"
end
end
# self is Employee
but when we call the instance method
employee = Employee.new
employee.say
# self is #<Employee:0x000055d5e3a00b90>
# self class is Employee
use self.name
VS name
VS @name
inside class:
1- self.name
use self
to force Ruby to work with instance methods like getter and setter
2- name
in instance methods will look for local variables first then instance methods
3- @name
will access the instance variable directly
in calling setter method you should use
self
to tell Ruby that you want to use the setter method of instance variable otherwise it will consider that you will set a local variablethe general rule from the Ruby style guide is to "Avoid self where not required." like inside an instance method to call getter, but it is good if we want to differentiate between instance variables and local variables with same names
class Employee
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def set_local_age(value)
# age here is local variable
age = value
# will use self to differentiate between age local variable and age instance variable
puts "Local variable age: #{age}"
puts "instance variable age: #{self.age}"
end
def set_instance_age(value)
# we need to set the instance variable age, so use self
self.age = value
# for getter it is fine now as we did not have age local variable, so age = self.age = getter method
puts "instance variable age: #{age}"
puts "instance variable age: #{self.age}"
end
def info
# no need to use self
self.age
end
end
employee = Employee.new("Ali", 20)
puts employee.age # 20
puts employee.set_local_age(30)
# Local variable age: 30
# instance variable age: 20
puts employee.age # 20
puts employee.set_instance_age(30)
# instance variable age: 30
# instance variable age: 30
puts employee.age # 30
puts employee.info # 30
Method Access Control
-
public
: can be called by anyone as there is no access control (default) -
private
: only accessible from inside the class in which it was defined (or one of its subclasses). -
protected
: are similar to private methods in that they cannot be invoked outside the class. The main difference between them is that protected methods can also be called from within other instances of the has the same method (from same class or subclass) , while private methods do not
private and protected instance methods
you can declare them by use
public
orprivate
orprotected
above group of methods , as a line divider so the access to methods will be controlled by the nearest access control keyword above itit is better to make code readable by use each access control only once in class and put all method under it, so you can have at most 3 groups. you can list all the public first and then the others
class Employee
def initialize(age)
@age = age
end
def work
do_something
end
def older?(other)
age > other.age
end
def other_do_something(other)
other.do_something
end
private
def do_something
puts 'I am working'
end
protected
attr_reader :age
end
ali = Employee.new(64)
ahmed = Employee.new(42)
ali.work # I am working
ali.do_something # => NoMethodError (private method `do_something' called for #<Employee:0x000055d9855bee10 @age=64>)
ali.other_do_something(ahmed) # => NoMethodError (private method `do_something' called for #<Employee:0x000056276856cad0 @age=42>)
ali.older?(ahmed) # => true
ahmed.older?(ali) # => false
ali.age # => NoMethodError (protected method `age' called for #<Employee:0x000055d9855bee10 @age=64>)
- Alternatively, you can set access levels of named methods by listing them as arguments to the access control functions.
class Employee
attr_reader :age
def initialize(age)
@age = age
end
def work
do_something
end
def older?(other)
age > other.age
end
def do_something
puts 'I am working'
end
def other_do_something(other)
other.do_something
end
protected :age
private :do_something
end
ali = Employee.new(64)
ahmed = Employee.new(42)
ali.work # I am working
ali.do_something # => NoMethodError (private method `do_something' called for #<Employee:0x0000561088ca9bf0 @age=64>)
ali.other_do_something(ahmed) # => NoMethodError (private method `do_something' called for #<Employee:0x0000561088ca9bf0 @age=42>)
ali.older?(ahmed) # => true
ahmed.older?(ali) # => false
ali.age # => NoMethodError (protected method `age' called for #<Employee:0x0000561088ca9bf0 @age=64>)
- or you can use the access control before the method definition (inlined)
class Employee
private def private_method
do_something
end
end
Employee.new.private_method # NoMethodError (private method `private_method' called for #<Employee:0x0000556fb1f57b90>)
private and protected class methods
- The
private
andprotected
macros only affect instance methods of the current scope, not class methods. - for private class methods you can use
private_class_method
, we did not have one for protected as it is not often used - you can also use define private and protected class methods by open singleton class
class Employee
def instance_method
self.class.private_method
end
def self.public_method
private_method
end
# will declare method access control after
def self.private_method
puts "Hi I am private"
end
private_class_method :private_method
# inline
private_class_method def self.private_method2
puts "Hi I am private too"
end
end
Employee.new.instance_method # NoMethodError (private method `private_method' called for Employee:Class)
Employee.public_method # Hi I am private
Employee.private_method # NoMethodError (private method `private_method' called for Employee:Class)
Employee.private_method2 # NoMethodError (private method `private_method2' called for Employee:Class)
Top comments (0)