Ruby OOP review
Tags: ruby, oop
Published: 2023-03-20 08:54:08

# Content
  - What is Object Oriented Programming (OOP)?
  - Ruby & OOP.
  - OOP principles.   
    - Abstraction.
    - Inheritance.
    - Polymorphism.
    - Encapsulation.
<br>
<br>


## What is OOP?
<br>

- OOP is a type of computer programming model that came as a solution for big and more complex code(complexity and size --> they become very difficult to maintain).

- based on the concept of "objects", which can contains:
  - Data: it called fields, attributes or properties.
  - Code: methods also called procedures.

- A common feature of objects is that methods are attached to them and can access and modify the object's data fields.

- Real-world Example:
  - `Employee` class, it has data (name, email, password, ...) and methods like (view_profile, forgot_password, change_email, ...)


<br>
## Ruby & OOP:
<br>

- Ruby is a pure object-oriented language and everything appears to Ruby as an object.

- Examples:

```ruby
12345.class        # => Integer
12345.object_id    # => 2469

'abcd'.class       # => String
'abcd'.object_id   # => 75260

nil.class         # => NilClass
nil.object_id     # => 8

employee = Employee.new   # <Employee:0x00000001233c5428
employee.class =>    # => Employee
employee.object_id   # => 75240

Employee.object_id  # => 72220
Employee.class      # => Class
```


## The main OOP principles:
  - Abstraction.
  - Inheritance.
  - Polymorphism.
  - Encapsulation.


### 1- Abstraction:
  - Showing only the main functionalities of a class and hiding non-essential implementation.

  - Example: (TV functionality) you switch it On, then you see the output on the screen, No matter what happens behind the scenes (electronic circuits... etc)
  - In software development: sending email class (think only on the main functionality, regardless of the rest of the code):
  - Reduce the complexity as much as we can.

```ruby
  WeeklyEmail.new(employee).send_mail
```
<br>
<br>
<br>

```ruby
  class WeeklyEmail
    def initialize(employee)
      self.employee = employee
    end

    def send_mail
      return if employee.email.blank?

      process_employee_weekly_email
    end

    private

    attr_accessor :employee

    ...

  end
```

  - Another form of Abstraction:
    - Abstract Classes & methods:

```ruby
  class ExportBase

    ...

    def process_file
      # Exporting logic that calls exporter_tool method
    end

    private

    def exporter_tool
      raise NotImplementedError
    end
  end


  class XlsExport < ExportBase
    def exporter_tool
      WriteXLSX
    end

    ...

  end



  class SpreadsheetExport < ExportBase
  end

  SpreadsheetExport.new.process_file(file) # will raise the exception
```

<br>

### 2- Inheritance:
  - Put shared attributes & methods in base class.
  - avoid reinventing the wheel.
  - Example:

```ruby
class Employee

  ...

  def delete
    validate
    delete_requests
    ...
    ...
    ...
    remove_from_database
  end

  ...

end
```

  - case 1: Inherting the implementation:

```ruby
class Manager < Employee

  ...

end
```

- case 2: Override the implementation: (To change the behavior)

```ruby
class Manager < Employee

  ...

  def delete
    validate
    delete_requests
    ...
    ...
    remove_from_database
    delete_his_subordinates
  end

  ...

end
```

- case 3: Using `super`:

```ruby
class Manager < Employee

  ...

  def delete
    super
    delete_his_subordinates
  end

  ...

end
```

<br>

### 3- Polymorphism:
  - having the same method with different implementations.
  - Example: The previous `Employee` and `Manager` classes, both are implementing the `delete` method with different implementations.
  - There are two forms of polymorphism:
    - overriding. Ex: The previous `delete` method in `Manager` class.
    - overloading. Ruby does NOT support overloading.
    - overloading example in java:

```java
int myMethod(int x)
float myMethod(float x, float y)
```

<br>
        overloading example in Ruby: (What will happen??)

```ruby
class Printer
  def show(param)
    puts param
  end  

  def show(param1, param2)
    puts param1
    puts param2
  end  
end

Printer.new.show 'Mohammed'
# => ArgumentError: wrong number of arguments (given 1, expected 2)

Printer.new.show 'Mohammed', 'Balfaqih'
# Mohammed
# Balfaqih
# => nil
```



### 4- Encapsulation:
  - How your data looks from the outside.
  - Prevents accidentally accessing and modifying our data.
  - In ruby Every data(instance_vriable) is private, and every method is public.
  - Example: 

```ruby
class Employee
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name, email)
    @first_name = first_name
    @last_name = last_name
    @email = email
  end

  def full_name
    first_name + " " + last_name
  end
end
```

```ruby
employee = Employee.new('Mohammed', 'Balfaqih', 'm.balfaqih@jisr.net')

employee.first_name
# => "Mohammed"

employee.full_name
# => "Mohammed Balfaqih"

employee.email
# NoMethodError (undefined method `email' for #<Employee:0x000000012f9bb9c0>)

employee.email = 'hacked-email@gmail.com'
# NoMethodError (undefined method `email=' for #<Employee:0x000000012f46d090>)
```

<br>

  - The solution here is `attr_reader` & `attr_writer` (getter & setter):

```ruby
class Employee
...
  attr_reader :first_name, :last_name
  attr_writer :email
...
end

employee.email = 'hacked-email@gmail.com'
# => "hacked-email@gmail.com"
```

---


## Next:
- super Vs. super()
- Multiple inheritance.
- Duck typing.
- Modules.