Contents

Object-Oriented Programming in Ruby

When we refer to object-oriented programming, we mean that our code is organized around objects. These objects are tangible instances that belong to various categories.

To illustrate this concept, let’s consider a car as an object. The class representing the car would be vehicle. A class serves as a blueprint for an object and outlines its attributes and behaviors. For a vehicle, the attributes might include the make, model, and whether it has a sunroof. These features are defined in the class, and each object (or instance) of the class shares these attributes, although the specific values may differ between instances. For instance, a Toyota Camry might be red, while a Honda Accord could be blue. This encapsulation of real-life scenarios into code is the essence of object-oriented programming, allowing us to create various instances from a single concept.

Defining a Class in Ruby

Class Syntax:

OOP Concepts

				
					class ClassName
end
				
			
Implementing OOP Concepts with Ruby

Ruby fully supports the object-oriented programming paradigm, enabling the creation of classes and their corresponding objects. As discussed, objects are instances of a class, and a class provides the framework for these objects. Each class specifies attributes and behaviors, while the objects represent real-world entities.

A class comprises two main components: data and methods. For instance, if we have a class called Vehicle, we can define the following attributes:

1. Make
2. Model

We need methods to access these attributes. To accomplish this, we use a constructor that takes values for the attributes and assigns them to the appropriate variables for each object.

Constructor Syntax:

				
					def initialize(attribute1, attribute2)
    @attribute1 = attribute1
    @attribute2 = attribute2
end

				
			

The initialize method is defined within the class, and its structure resembles a standard function. It can accept any number of arguments. The @ symbol denotes the object’s attributes.

Example:

				
					class Vehicle
    def initialize(make, model)
        @make = make
        @model = model
    end
end

				
			

To create an instance of a class, we use a familiar method similar to creating hashes or arrays. We utilize the new function.

Syntax:

				
					object_name = ClassName.new()

				
			

If there are parameters, they are typically passed in parentheses with new, similar to regular methods.

Example:

				
					class Vehicle
    def initialize(make, model)
        @make = make
        @model = model
    end
end

car1 = Vehicle.new('Toyota', 'Camry')

				
			

Now we have an object named car1. Calling Vehicle.new() creates a new instance, invoking the initialize method. Thus, car1.make will be Toyota and car1.model will be Camry.

Let’s create a second object as well.

Example:

				
					class Vehicle
    def initialize(make, model)
        @make = make
        @model = model
    end

    # Defining Methods
    def get_make
        @make
    end

    def get_model
        @model
    end
end

# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')

puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model

puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model

				
			

Output:

				
					Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord

				
			

In this example, calling car1.get_make invokes the get_make method for car1, returning the result ‘Toyota’. Similarly, calling car2.get_model invokes the get_model method for car2, yielding ‘Accord’.

Typically, methods in Ruby return the last evaluated result, so we don’t need to explicitly use the return keyword.

Instead of the following code:

				
					def get_make
    return @make
end

				
			

We can simply write:

				
					def get_make
    @make
end

				
			
Variable Scope

Variable scope refers to the areas in which a variable can be utilized. It can be global or local. Global variables are accessible throughout the code. To define a global variable, we use the $ symbol.

Example:

				
					class Vehicle
    # Creating global variable
    $global_var = 'GLOBAL'

    def initialize(make, model)
        @make = make
        @model = model
    end

    # Defining Methods
    def get_make
        @make
    end

    def get_model
        @model
    end
end

# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')

puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model

puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model

# Printing global variable
puts 'Global variable: ' + $global_var

				
			

Output:

				
					Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord
Global variable: GLOBAL

				
			

In the example above, we declared a global variable $global_var. Since it is global, it can be accessed anywhere in the program. The value of $global_var is ‘GLOBAL’. If we attempted to access car1.make, it would produce an error because that variable is locally scoped to the class.

Modifying Attributes

Let’s illustrate this concept with another example.

Example:

				
					class Vehicle
    def initialize(make, model)
        @make = make
        @model = model
    end

    # Defining Methods
    def get_make
        @make
    end

    def get_model
        @model
    end

    def change_model(new_model)
        @model = new_model
    end
end

# Creating object
car = Vehicle.new('Toyota', 'Camry')
puts 'Make of car: ' + car.get_make
puts 'Model of car: ' + car.get_model

# Modifying attribute
car.change_model('Corolla')
puts 'Updated model of car: ' + car.get_model

				
			

Output:

				
					Make of car: Toyota
Model of car: Camry
Updated model of car: Corolla

				
			

In this example, we introduce a new method, change_model, which allows us to modify the value of the model attribute. Since the attributes cannot be accessed directly outside the class, we use methods to manipulate them. As shown, we pass a new model as an argument to the change_model method, which updates the object’s model attribute accordingly.

Class Variables

Class variables differ from instance variables in that they belong to the class itself rather than individual objects. For instance variables like make and model, each object maintains its own copy, while class variables share a single copy among all objects. Class variables can be accessed by the instances of the class and are denoted by @@.

Example:

				
					class Vehicle
    @@count = 0

    def initialize(make, model)
        @make = make
        @model = model
        @@count += 1
    end

    # Defining Methods
    def get_make
        @make
    end

    def get_model
        @model
    end

    def self.get_count
        @@count
    end
end

# Creating objects
car1 = Vehicle.new('Toyota', 'Camry')
car2 = Vehicle.new('Honda', 'Accord')

puts 'Make of car1: ' + car1.get_make
puts 'Model of car1: ' + car1.get_model

puts 'Make of car2: ' + car2.get_make
puts 'Model of car2: ' + car2.get_model

puts 'Total number of vehicles created: ' + Vehicle.get_count.to_s

				
			

Output:

				
					Make of car1: Toyota
Model of car1: Camry
Make of car2: Honda
Model of car2: Accord
Total number of vehicles created: 2

				
			

In this example, we utilize a class variable @@count to track the number of objects created. We initialize count to 0, and each time an object is instantiated, the initialize method increments the count. Regardless of whether we call get_count using car1 or car2, it returns the same value, indicating the total number of vehicle objects created.

Using Accessor Methods

Instead of manually creating methods to access attributes, Ruby provides a simpler approach using attr_reader, attr_writer, and attr_accessor. Let’s see how these work in the following example.

Example:

				
					class Vehicle
    attr_reader :make
    attr_writer :model

    def initialize(make, model)
        @make = make
        @model = model
    end
end

car = Vehicle.new('Toyota', 'Camry')
puts 'Make of car: ' + car.make
puts 'Model of car: ' + car.model

# Changing the model
car.model = 'Corolla'
puts 'Updated model of car: ' + car.model

				
			

Output:

				
					Make of car: Toyota
Model of car: Camry
Updated model of car: Corolla

				
			

Explanation: The expression is evaluated as:

				
					(10 * 5) + (8 / 2) = 50 + 4 = 54
				
			

Example 1: C Program to Calculate the Area and Perimeter of a Rectangle

				
					#include <stdio.h>

int main() {
    // Declare dimensions of the rectangle
    int length = 7;
    int width = 3;

    // Declare variables to store the area and perimeter
    int area, perimeter;

    // Calculate area
    area = length * width;

    // Calculate perimeter
    perimeter = 2 * (length + width);

    // Print the results
    printf("Area = %d\n", area);
    printf("Perimeter = %d\n", perimeter);

    return 0;
}

				
			

Output:

				
					Area = 21
Perimeter = 20

				
			

Inheritance

Inheritance is a key principle in object-oriented programming, allowing one class to inherit the properties and methods of another. The class being inherited from is known as the “base” or “parent” class, while the inheriting class is the “derived” or “child” class.

Syntax:

				
					class BaseClass
  # code for base class
end

class DerivedClass < BaseClass
  # code for derived class
end

				
			

The < symbol indicates inheritance. The derived class inherits all attributes and methods from the base class. Note that this relationship is one-way: the base class does not inherit anything from the derived class.

Example:

Suppose we have a base class Device and two derived classes: Laptop and Smartphone.

				
					# Ruby program demonstrating Inheritance
class Device
  def initialize(device_name, device_color)
    @device_name = device_name
    @device_color = device_color
  end

  def description
    puts 'This is a general device.'
  end
end

class Laptop < Device
  def description
    puts 'This is a laptop.'
  end
end

class Smartphone < Device
  def details
    puts 'This is a smartphone.'
  end
end

# Creating objects
laptop = Laptop.new('Dell', 'Silver')
smartphone = Smartphone.new('iPhone', 'Black')

laptop.description
smartphone.description
smartphone.details

				
			

Output:

				
					This is a laptop.
This is a general device.
This is a smartphone.

				
			

In the above code, the Laptop and Smartphone classes inherit from the Device class. The Laptop class overrides the description method, while the Smartphone class adds a new method called details.

Using super

If a derived class has a method with the same name as a method in the base class but wants to access the base class’s method as well, it can use the super keyword.

Example:

				
					# Ruby program using super
class Device
  def initialize(device_name, device_color)
    @device_name = device_name
    @device_color = device_color
  end

  def description
    puts 'This is a general device.'
  end
end

class Laptop < Device
  def description
    puts 'This is a laptop.'
    super
  end
end

laptop = Laptop.new('Dell', 'Silver')
laptop.description

				
			

Output:

				
					This is a laptop.
This is a general device.

				
			

In this case, the Laptop class calls super to invoke the description method from the Device class, executing both the derived and base class methods.

Attributes in the Derived Class

If the derived class needs its own attributes in addition to those from the base class, you can pass the base class attributes using super.

				
					# Ruby program with derived class attributes
class Device
  attr_accessor :device_name, :device_color

  def initialize(device_name, device_color)
    @device_name = device_name
    @device_color = device_color
  end
end

class Laptop < Device
  attr_accessor :model

  def initialize(device_name, device_color, model)
    super(device_name, device_color)
    @model = model
  end
end

laptop = Laptop.new('Dell', 'Silver', 'XPS')
puts laptop.device_name
puts laptop.device_color
puts laptop.model

				
			

Output:

				
					Dell
Silver
XPS

				
			

In this example, the Laptop class has an additional attribute model. When an object is created, super is used to initialize the attributes of the Device class before initializing the Laptop-specific attributes.

Modules

Modules are containers for methods and constants that can be reused in multiple classes. Unlike classes, modules cannot create objects. They are often used to group related methods and constants and can be included in classes.

Example:

				
					module Features
  FEATURE_ONE = 'Wi-Fi'
  FEATURE_TWO = 'Bluetooth'

  def feature_list
    puts 'This device supports Wi-Fi and Bluetooth.'
  end
end

				
			

To use the data and methods in a module, include the module in a class using the include keyword.

Mixins with include

Mixins allow classes to include modules, effectively adding the module’s methods to the class.

Example:

				
					# Ruby program using mixins
module Features
  def feature_list
    puts 'This device supports Wi-Fi and Bluetooth.'
  end
end

class Laptop
  include Features
  attr_reader :model

  def initialize(model)
    @model = model
  end
end

laptop = Laptop.new('XPS')
laptop.feature_list
puts laptop.model

				
			

Output:

				
					This device supports Wi-Fi and Bluetooth.
XPS

				
			

Mixins with extend

Using extend incorporates the module’s methods at the class level, allowing the class itself to use the module’s methods instead of instances.

Example:

				
					# Ruby program extending mixins
module Features
  def feature_list
    puts 'This class supports advanced features.'
  end
end

class Laptop
  extend Features
  attr_reader :model

  def initialize(model)
    @model = model
  end
end

Laptop.feature_list
laptop = Laptop.new('XPS')
puts laptop.model

				
			

Output:

				
					This class supports advanced features.
XPS

				
			

In this case, extend allows the Laptop class itself to use the feature_list method, rather than an instance of the class.

Public and Private Methods

By default, all methods in a class are public. You can explicitly set methods as private to restrict their access.

Example:

				
					# Ruby program demonstrating public and private methods
class Device
  def initialize(device_name, device_color)
    @device_name = device_name
    @device_color = device_color
  end

  public
  def display_info
    greet
    puts "Device name: #{@device_name}"
    puts "Device color: #{@device_color}"
  end

  private
  def greet
    puts 'Hello, user!'
  end
end

device = Device.new('Laptop', 'Silver')
device.display_info

				
			

Output:

				
					Hello, user!
Device name: Laptop
Device color: Silver

				
			

Ruby Class & Object

Ruby is a powerful object-oriented programming language. Some of the key characteristics of object-oriented programming include encapsulation, inheritance, polymorphism, data abstraction, and operator overloading. In Ruby, classes and objects play a central role in structuring code.

A class can be viewed as a template or blueprint from which objects (instances of the class) are created. For instance, if we think of a class named “Vehicle,” specific types of vehicles such as cars, trucks, and bikes would be objects or instances of that class. Similarly, if “Employee” is a class, objects of that class might be a manager, developer, or assistant.

Defining a Class in Ruby:

In Ruby, defining a class is straightforward. You start with the class keyword, followed by the name of the class. The name of the class should begin with a capital letter.

Syntax:

				
					class ClassName
  # class body
end

				
			

A class is encapsulated between the class and end keywords. All the data members (attributes) and methods are defined inside this block.

Example:

				
					# Class named Animal
class Animal

  # Class variables
  @@num_legs = 4
  @@num_eyes = 2

end

				
			
Creating Objects using the new Method in Ruby:

Objects are instances of a class, and they are easily created using the new method. Multiple objects can be created from a single class.

Syntax:

				
					object_name = ClassName.new

				
			

Example:

				
					# Class named Book
class Book

  # Class variable
  @@pages = 100

end

# Creating objects of the Book class
novel = Book.new
magazine = Book.new

				
			

In this example, Book is the class, and @@pages is a class variable. We create two objects, novel and magazine, using the new method.

Defining Methods in Ruby:

Methods in Ruby are defined using the def keyword followed by the method name. By convention, method names are in lowercase. A method is terminated with the end keyword.

Syntax:

				
					def method_name
  # method body
end

				
			

Example:

				
					# Ruby program to demonstrate methods

# Defining class Calculator
class Calculator

  # Defining a method
  def add
    # Performing addition
    puts 5 + 10
  end

end

# Creating an object of Calculator
calc = Calculator.new

# Calling the method
calc.add

				
			

Output:

				
					15

				
			
Passing Parameters to the new Method:

You can pass parameters to the new method, which are used to initialize instance variables within the class. To handle parameters, you need to define an initialize method. The initialize method is a special method in Ruby that is automatically invoked when the new method is called with arguments.

Example:

				
					# Ruby program to illustrate passing parameters 
# to the new method

# Defining class Laptop
class Laptop

  # initialize method
  def initialize(brand, model, price)
    # Instance variables
    @laptop_brand = brand
    @laptop_model = model
    @laptop_price = price

    # Displaying values
    puts "Brand: #@laptop_brand"
    puts "Model: #@laptop_model"
    puts "Price: $#@laptop_price"
    puts "\n"
  end

end

# Creating objects and passing parameters 
# to the new method
laptop1 = Laptop.new("Dell", "XPS 13", 1200)
laptop2 = Laptop.new("Apple", "MacBook Air", 999)

				
			

Output:

				
					Brand: Dell
Model: XPS 13
Price: $1200

Brand: Apple
Model: MacBook Air
Price: $999

				
			

Private Classes in Ruby

Ruby has a unique approach to method visibility compared to other languages like Java. In Ruby, it’s more about which object is calling the method, as everything in Ruby, including classes, is an object.

Private Classes in Ruby

When a class or constant is declared private in Ruby, it means the class cannot be called with an explicit receiver. A private class or constant can only be accessed with an implicit receiver. In Ruby, private classes can be defined inside another class as nested sub-classes and declared as private constants. This makes the private class accessible only from within the outer class.

Syntax:

				
					private_constant :ClassName

				
			

Example:

				
					# Defining the outer class
class Universe

  # Defining a public sub-class
  class Heroes
    def show
      puts "Superman saves the day!"
    end
  end

  # Another public sub-class
  class Villains
    def show
      puts "Lex Luthor schemes again!"
    end
  end
end

# Calling the public methods
Universe::Heroes.new.show
Universe::Villains.new.show

				
			

Output:

				
					Superman saves the day!
Lex Luthor schemes again!

				
			

In this example, both the Heroes and Villains classes are public, meaning they can be accessed directly using an explicit receiver.

Example of a Private Class:

				
					# Defining the outer class
class Universe

  # Defining a private sub-class
  class Heroes
    def show
      puts "Superman saves the day!"
    end
  end

  # Defining a public sub-class
  class Villains
    def show
      puts "Lex Luthor schemes again!"
    end
  end

  # Making the Heroes class private
  private_constant :Heroes
end

# Calling the public method of Villains class
Universe::Villains.new.show

# Attempting to access the private class (will raise an error)
Universe::Heroes.new.show

				
			

Output:

				
					Lex Luthor schemes again!
main.rb:18:in `<main>': private constant Universe::Heroes referenced (NameError)

				
			

In this example, since the Heroes class is declared private using private_constant, it cannot be accessed directly by external code, while the Villains class remains publicly accessible.

Accessing a Private Class from the Outer Class

Although private classes cannot be accessed explicitly from outside the outer class, they can still be accessed from within the outer class. Here’s an example demonstrating how this works:

				
					# Defining the outer class
class Universe

  # Defining a private sub-class
  class Heroes
    def show
      puts "Superman saves the day!"
    end
  end

  # Defining another private sub-class
  class Villains
    def show
      puts "Lex Luthor schemes again!"
    end
  end

  # Method inside outer class to access private classes
  def battle
    Heroes.new.show
    Villains.new.show
  end

  # Making the sub-classes private
  private_constant :Heroes
  private_constant :Villains
end

# Accessing the private classes through the outer-class method
Universe.new.battle

# Attempting to access private classes explicitly (will raise an error)
Universe::Heroes.new.show
Universe::Villains.new.show

				
			

Output:

				
					Superman saves the day!
Lex Luthor schemes again!
main.rb:25:in `<main>': private constant Universe::Heroes referenced (NameError)
main.rb:26:in `<main>': private constant Universe::Villains referenced (NameError)

				
			

Freezing Objects Ruby

In Ruby, any object can be frozen by invoking the Object#freeze method. Once an object is frozen, it becomes immutable: its instance variables cannot be altered, new singleton methods cannot be defined, and if it’s a class or module, you cannot modify its methods. To check if an object is frozen, you can use the Object#frozen? method, which returns true if the object is frozen and false otherwise.

The freeze method essentially turns an object into a constant by preventing any further modifications. It’s important to note that once an object is frozen, it cannot be unfrozen.

Syntax:

				
					ObjectName.freeze

				
			

Example:

				
					# Ruby program to demonstrate freezing of an object

# Define a simple class for Product
class Product
  # Constructor to initialize product details
  def initialize(name, price)
    @name = name
    @price = price
  end

  # Accessor methods to retrieve values
  def name
    @name
  end

  def price
    @price
  end

  # Setter methods to modify values
  def name=(new_name)
    @name = new_name
  end

  def price=(new_price)
    @price = new_price
  end
end

# Create a new Product object
item = Product.new("Laptop", 1000)

# Freeze the object to prevent modification
item.freeze

# Check if the object is frozen
if item.frozen?
  puts "The product object is frozen and cannot be modified."
else
  puts "The product object is still modifiable."
end

# Attempt to modify the frozen object
begin
  item.name = "Tablet"  # This will raise an error
  item.price = 800
rescue => e
  puts e.message  # Outputs error message due to modification attempt
end

# Access and print the unchanged values
puts "Product Name: #{item.name}"
puts "Product Price: #{item.price}"

				
			

Output:

				
					The product object is frozen and cannot be modified.
can't modify frozen Product
Product Name: Laptop
Product Price: 1000

				
			

Polymorphism in Ruby

In Ruby, variables do not have specific types like in other programming languages. Every variable is considered an object, and each object can be modified individually. You can also add methods or functions to every object dynamically. Object-Oriented Programming (OOP) plays a significant role in this flexibility. Among the core principles of OOP are Inheritance, Encapsulation, and Polymorphism.

Polymorphism is one such concept. The term comes from two Greek words: “Poly” meaning “many” and “Morph” meaning “forms.” Essentially, polymorphism allows one method to execute in different ways depending on the object it’s acting on. By passing different objects to the same method, we can achieve different outcomes, avoiding the need for lengthy if-else statements.

In Ruby, polymorphism allows classes to have different functionalities while sharing a common interface. Polymorphism can be achieved in two main ways:

  • Polymorphism using Inheritance
  • Polymorphism using Duck-Typing
Polymorphism using Inheritance

Inheritance allows a child class to inherit properties and methods from a parent class. Ruby makes it easy to implement polymorphism using inheritance. Here’s an example:

Example:

				
					# Ruby example of Polymorphism using Inheritance

class Instrument
  def play
    puts "Playing an instrument"
  end
end

# Using inheritance
class Guitar < Instrument
  def play
    puts "Playing the guitar"
  end
end

# Using inheritance
class Piano < Instrument
  def play
    puts "Playing the piano"
  end
end

# Creating objects and calling the same method
instrument = Instrument.new
instrument.play

instrument = Guitar.new
instrument.play

instrument = Piano.new
instrument.play

				
			

Output:

				
					Playing an instrument
Playing the guitar
Playing the piano

				
			
Polymorphism using Duck-Typing

In Ruby, we focus on what an object can do rather than its class. This is known as Duck-Typing, where an object’s abilities are more important than its type. The idea is: “If it looks like a duck and quacks like a duck, it’s probably a duck.” Here’s an example to illustrate this concept:

Example:

				
					# Ruby example of Polymorphism using Duck Typing

# Defining a Store class
class Store
  def order(customer)
    customer.payment_type
    customer.discount
  end
end

# Defining two different customer classes
class RegularCustomer
  def payment_type
    puts "Payment with credit card"
  end

  def discount
    puts "No discount available"
  end
end

class PremiumCustomer
  def payment_type
    puts "Payment with loyalty points"
  end

  def discount
    puts "10% discount applied"
  end
end

# Polymorphism in action
store = Store.new

puts "Regular customer making a purchase:"
customer = RegularCustomer.new
store.order(customer)

puts "\nPremium customer making a purchase:"
customer = PremiumCustomer.new
store.order(customer)

				
			

Output:

				
					Regular customer making a purchase:
Payment with credit card
No discount available

Premium customer making a purchase:
Payment with loyalty points
10% discount applied

				
			

Ruby Constructors

A constructor is a special method in a class that is automatically invoked when an instance of the class is created. Like regular methods, constructors can contain a set of instructions that are executed upon object creation.

Key Points about Constructors:
  • A constructor is defined using the initialize and def keywords.
  • It is treated as a special method in Ruby.
  • Constructors cannot be overloaded in Ruby.
  • Constructors are not inherited.
  • They return an instance of the class.
  • Note: Whenever an object of a class is created using the new method, the initialize method is called internally. Any arguments passed to new are automatically forwarded to the initialize method.
  • Unlike other programming languages, constructors in Ruby have a unique name.

Syntax:

				
					# Ruby program demonstrating public access control

class Greeting
  # Public method without the public keyword
  def hello
    puts "Hello, world!"
  end

  # Using public keyword to define public methods
  public
  def good_morning
    puts "Good morning!"
  end
  
  def good_night
    puts "Good night!"
    # Calling hello method
    hello
    # Calling hello using self
    self.hello
  end
end

# Creating an object
greet = Greeting.new

# Calling public methods
greet.hello
greet.good_morning
greet.good_night

				
			

Output:

				
					Hello, world!
Good morning!
Good night!
Hello, world!
Hello, world!

				
			
Private Methods

Private methods cannot be accessed outside the class; they can only be called from within the class. Unlike public methods, you cannot use the self keyword to call private methods. The initialize method, which is used to initialize instance variables, is inherently private and cannot be made public.

Example:

				
					# Ruby program demonstrating private access control

class Person
  def initialize(name)
    @name = name
    puts "Person's name is #{@name}."
  end

  # Public method
  def greet
    puts "Hello!"
    # Calling private method
    private_greet
  end

  private
  # Private method
  def private_greet
    puts "This is a private greeting."
  end
end

# Creating an object
person = Person.new("John")

# Calling public method
person.greet

# Attempting to call private method (will raise an error)
person.private_greet

				
			

Output:

				
					Person's name is John.
Hello!
This is a private greeting.
source_file.rb:27:in `<main>': private method `private_greet' called for #<Person:...> (NoMethodError)

				
			
Protected Methods

Protected methods can be called only within the class and its subclasses. These methods are not accessible outside the class definition or by external objects. However, they can be accessed by any object of the same class or its subclass.

Example:

				
					# Ruby program demonstrating protected access control

class Animal
  def initialize(type)
    @type = type
    # Calling protected method
    protected_sound
  end

  # Public method
  def show_type
    puts "This is a #{@type}."
  end

  protected
  # Protected method
  def protected_sound
    puts "This animal makes a sound."
  end
end

class Dog < Animal
  def bark
    puts "The dog barks!"
    # Calling protected method from parent class
    protected_sound
  end
end

# Creating objects
animal = Animal.new("generic animal")
dog = Dog.new("dog")

# Calling public method
animal.show_type
dog.bark

# Attempting to call protected method from outside (will raise an error)
dog.protected_sound

				
			
Ruby Access Control

Access control is an essential part of object-oriented programming that restricts the visibility of methods and fields to protect data from accidental modification. Ruby, however, approaches access control somewhat differently than many other languages.

Key Points:

1. Instance and Class Variables: Always private in terms of visibility.
2. Applicability: Access controls in Ruby only apply to methods, not to instance or class variables.
3. Inheritance: Private methods in Ruby can be inherited, similar to public and protected methods.
4. Access Control Conditions: It depends on where the method is called (inside or outside the class definition) and whether the self keyword is used.

Public Methods

Public methods can be called by anyone. By default, all methods in Ruby are public unless explicitly declared otherwise.

Example:

				
					# Ruby program demonstrating calling private and protected methods inside a public method

class ExampleClass
  # Public method
  def method_one
    puts "Public method_one is called"

    # Calling protected and private methods within the public method
    method_two
    method_three
  end

  # Defining a protected method
  protected

  def method_two
    puts "Protected method_two is called"
  end

  # Defining a private method
  private

  def method_three
    puts "Private method_three is called"
  end
end

# Creating an object of the class
obj = ExampleClass.new

# Calling the public method
obj.method_one

				
			

Output:

				
					Public method_one is called
Protected method_two is called
Private method_three is called

				
			
Inheritance of Private Methods

Unlike many other object-oriented programming languages, Ruby allows private methods to be inherited by subclasses.

Example:

				
					# Ruby program demonstrating calling private and protected methods inside a public method

class ExampleClass
  # Public method
  def method_one
    puts "Public method_one is called"

    # Calling protected and private methods within the public method
    method_two
    method_three
  end

  # Defining a protected method
  protected

  def method_two
    puts "Protected method_two is called"
  end

  # Defining a private method
  private

  def method_three
    puts "Private method_three is called"
  end
end

# Creating an object of the class
obj = ExampleClass.new

# Calling the public method
obj.method_one

				
			

Output:

				
					Public method_one is called
Protected method_two is called
Private method_three is called

				
			

Ruby Encapsulation

Encapsulation is the concept of wrapping data and the methods that operate on that data into a single unit, typically a class. This mechanism binds code and the data it manipulates, providing a protective shield that restricts access to the data directly from outside the class.

In technical terms, encapsulation in Ruby involves hiding the internal state of an object and requiring all interactions to be performed through methods. The data (or variables) of a class are typically made private and are accessible only through public methods within the class.

Encapsulation is achieved in Ruby by defining instance variables as private and using public methods to get and set the values of those variables.

Example:

				
					# Ruby program demonstrating encapsulation

class Customer
  def initialize(id, name, address)
    # Instance variables
    @customer_id = id
    @customer_name = name
    @customer_address = address
  end

  # Method to display customer details
  def display_details
    puts "Customer ID: #{@customer_id}"
    puts "Customer Name: #{@customer_name}"
    puts "Customer Address: #{@customer_address}"
  end
end

# Creating objects of the Customer class
customer1 = Customer.new("1", "Alice", "123 Maple Street")
customer2 = Customer.new("2", "Bob", "456 Oak Avenue")

# Calling methods to display details
customer1.display_details
customer2.display_details

				
			

Output:

				
					Customer ID: 1
Customer Name: Alice
Customer Address: 123 Maple Street
Customer ID: 2
Customer Name: Bob
Customer Address: 456 Oak Avenue

				
			

Ruby Mixins

Before diving into Ruby Mixins, it’s helpful to understand the basics of Object-Oriented Programming. If you’re not familiar with these concepts, it might be good to review them first. In object-oriented languages, a class can sometimes inherit features from more than one parent class. This is known as multiple inheritance. However, Ruby doesn’t support multiple inheritance directly; instead, it uses a feature called mixins.

Mixins in Ruby allow modules to “mixin” their methods into a class using the include method. This provides a controlled way to add functionalities to classes. When a class includes a module, the module’s methods become part of that class. In Ruby, a module that is included in a class is referred to as a mixin.

Example:

				
					# Ruby program demonstrating mixins

# Module containing two methods
module ModuleA
  def method1
    puts 'This is method1 from ModuleA.'
  end
  
  def method2
    puts 'This is method2 from ModuleA.'
  end
end

# Another module containing two methods
module ModuleB
  def method3
    puts 'This is method3 from ModuleB.'
  end
  
  def method4
    puts 'This is method4 from ModuleB.'
  end
end

# Creating a class that includes both modules
class SampleClass
  include ModuleA
  include ModuleB
  
  def class_method
    puts 'This is a method from SampleClass.'
  end
end

# Creating an object of the class
sample_object = SampleClass.new

# Calling methods from the included modules
sample_object.method1
sample_object.method2
sample_object.method3
sample_object.method4
sample_object.class_method

				
			

Output:

				
					This is method1 from ModuleA.
This is method2 from ModuleA.
This is method3 from ModuleB.
This is method4 from ModuleB.
This is a method from SampleClass.

				
			

Instance Variables in Ruby

Ruby has four different types of variables: Local variables, Instance variables, Class variables, and Global variables. An instance variable in Ruby starts with the @ symbol and its scope is limited to the specific object in which it resides. Even though two objects may belong to the same class, they can have different values for their instance variables.

Characteristics of Instance Variables in Ruby

1. Instance variables have a value of nil before they are initialized.
2. All instance variables are private by default.
3. An object’s instance variables can only be accessed by the instance methods of that specific object.
4. Ruby’s instance variables do not need explicit declaration, providing a flexible object structure.
5. Every instance variable is dynamically added to an object when it is first referenced.
6. An instance variable belongs to the object itself; each object of a class has its own set of instance variables.
7. One instance object can modify the values of its instance variables without affecting any other instance.
8. An instance variable can be used by multiple class methods, except when the method is static.

Example :

				
					# Ruby program demonstrating instance variables using a constructor

class Person

  # Constructor
  def initialize()
    # Instance variable
    @name = "John Doe"
  end

  # Method to display details
  def display_details
    puts "Person's name is #@name"
  end

end

# Creating an object of the class Person
person = Person.new()

# Calling the method to display instance variable
person.display_details

				
			

Output:

				
					Person's name is John Doe

				
			

Data Abstraction in Ruby

Data abstraction refers to representing important details while hiding the inner workings of a system. This concept is key in object-oriented programming. In Ruby, abstraction allows developers to focus on a few high-level functionalities at a time by isolating the implementation details from the interface.

Real-life Examples of Abstraction Making a Phone Call:

When making a call, a person simply dials the number and presses the call button. The caller does not need to understand the intricate working of the phone’s internal system. This separation of user interaction and underlying complexity exemplifies abstraction.
Using a Television: Users can turn the television on or off, change channels, or adjust the volume without knowing the internal mechanics of how these features work. This simplified interface hides the complex implementation behind it.

Data Abstraction in Modules

In Ruby, modules are collections of methods, classes, and constants. For example, the sqrt() method in the Math module is used to calculate the square root of a number. Users call this method without needing to know the internal algorithm that performs the calculation.

Data Abstraction in Classes

Classes allow data abstraction by grouping information and methods using access control (private, protected, and public). By using these access specifiers, a class can decide which information is accessible and which is hidden.

Data Abstraction Using Access Control

Ruby provides three levels of access control: public, private, and protected. These are used to implement data abstraction in classes:

  • Public Members: Can be accessed from anywhere in the program.
  • Private Members: Can only be accessed from within the class in which they are declared. They cannot be accessed from outside the class.

Example: Data Abstraction Using Access Control

				
					# Ruby program to demonstrate data abstraction

class Person

  # Defining a public method
  public 

  def display_info
    puts "This is a public method."
    # Calling a private method inside the public method
    secret_method
  end

  # Defining a private method
  private

  def secret_method
    puts "This is a private method."
  end
end

# Creating an object of the class Person
person = Person.new

# Calling the public method of the class Person
person.display_info

				
			

Output:

				
					This is a public method.
This is a private method.

				
			
Static Keywords in Ruby

In many programming languages, the static keyword is primarily used for memory management and sharing the same method or variable across multiple objects of a class. While Ruby does not have an explicit static keyword, similar functionality can be achieved using class variables and class methods.

Static Variables in Ruby

Ruby allows for class-level variables, often referred to as static variables in other languages. These variables are shared across all instances of a class. In Ruby, static variables are implemented using class variables. Class variables begin with @@ and have a lifespan that spans the entire lifetime of the program.

Example: Static Variable Using Class Variable

Here’s an example demonstrating the use of a static (class) variable in Ruby:

				
					# Ruby program to demonstrate static variable

class Student
  # Class variable
  @@student_count = 0

  def initialize
    @@student_count += 1
    puts "Number of Students = #{@@student_count}"
  end
end

# Creating objects of the class Student
s1 = Student.new
s2 = Student.new
s3 = Student.new
s4 = Student.new

				
			

Output:

				
					Number of Students = 1
Number of Students = 2
Number of Students = 3
Number of Students = 4