OOP in Ruby Explained with Easy Examples

OOP in Ruby with Real Examples and Rails Use Cases

๐Ÿงฑ OOP in Ruby for Rails Developers

๐Ÿ“˜ Detailed Explanation: OOP in Ruby Made Simple

Object-Oriented Programming (OOP) is a way to structure your code by organizing it into โ€œobjectsโ€. In Ruby, **everything is an object** โ€” numbers, strings, even classes themselves.

๐Ÿง  What is an Object?

An object is like a real-world thing. It has:

  • Data (Attributes): Like a personโ€™s name or age
  • Behavior (Methods): Like walking or talking

๐Ÿ—๏ธ What is a Class?

A class is like a blueprint. For example:

class Dog
  def bark
    "Woof!"
  end
end

This doesnโ€™t create a dog yet. It’s just the idea of a dog.

๐Ÿงช Creating an Object

Now let’s create a real dog using the class:

dog = Dog.new
puts dog.bark  # => \"Woof!\"

๐Ÿงฑ Why Use OOP?

  • ๐Ÿ‘จโ€๐Ÿ”ง Organize your code better
  • โ™ป๏ธ Reuse code across your app
  • ๐Ÿงช Make testing and debugging easier
  • ๐Ÿ”’ Protect your data using private methods
  • ๐Ÿš€ Scale your app easily as it grows

๐Ÿ’ก Example in a Rails App

In Rails, everything you use is object-oriented:

  • User.find(1) is an object calling a method
  • @user.name is calling the name method on an object
  • Each model, controller, and helper is a class

โœ… Summary

OOP helps you write clean, organized, and powerful Ruby code. It’s not just a concept โ€” it’s how Ruby and Rails are built from the ground up.

๐Ÿ“š Key Terms and Best Concepts in OOP (Ruby)

๐Ÿงฉ Term๐Ÿ“ Description
ClassA blueprint for creating objects. Example: class User
ObjectAn instance of a class. It holds data and methods.
MethodA function defined inside a class that performs actions. Example: def greet
Instance VariableA variable that holds data inside an object, prefixed with @. Example: @name
InitializeSpecial method called when an object is created. Acts like a constructor. Example: def initialize(name)
EncapsulationHides internal details and keeps data safe. Achieved using private/protected methods.
InheritanceOne class can inherit behavior from another. Use < symbol. Example: class Admin < User
PolymorphismDifferent classes can define the same method in their own way.
ModuleA collection of methods that can be included in classes using include.
selfRefers to the current object or class context. Often used in class methods.
superCalls the same method from the parent class. Used in overridden methods.
attr_accessorCreates getter and setter methods for instance variables.
PrivateMethods not accessible outside the class or object context.
Duck Typingโ€œIf it walks like a duck and quacks like a duck, it’s a duck.โ€ Focus on behavior, not class.
Dynamic DispatchRuby determines method calls at runtime, allowing flexible code behavior.

๐Ÿ”„ Flow of OOP in Ruby & Where Itโ€™s Used

๐Ÿงญ Step-by-Step Flow โ€“ How OOP Works in Ruby

  1. Define a Class:
    Create a class to describe the structure and behavior of the object.
  2. Add Attributes & Methods:
    Use instance variables (e.g., @name) and methods (e.g., def speak).
  3. Initialize with Data:
    Use initialize to pass data when creating the object.
  4. Create Object (Instance):
    Use ClassName.new to create an object.
  5. Call Methods:
    Use the object to access methods or data (e.g., user.greet).
  6. Extend with Inheritance or Modules:
    Use inheritance (<) or modules (include) to reuse logic.
  7. Use in Application Logic:
    Use objects in controllers, services, and models in Rails.

๐ŸŒ Real-World Use Areas in Ruby & Rails

  • ๐Ÿ‘ฅ Models in Rails: Each model (e.g., User) is a class that behaves like an object
  • ๐Ÿ” Service Objects: Business logic wrapped inside reusable classes
  • ๐Ÿ“ฆ Background Jobs: Sidekiq/ActiveJob uses job classes and workers
  • ๐Ÿ“ฎ Mailers: Email logic wrapped in object-oriented classes
  • ๐Ÿ“ Controllers: Each controller is a class handling request-response logic
  • ๐Ÿ“„ Form Objects: Classes used to combine multiple models or validations
  • ๐Ÿ” Policy Objects: Used in authorization frameworks like Pundit
  • ๐Ÿงช Testing: Objects are easier to mock and test individually
  • ๐Ÿ”„ Serializers: Object representations of your data for APIs (e.g., ActiveModel::Serializer)
  • ๐Ÿ”ง Custom Validators: Use classes to define custom validation logic

โœ… Summary

OOP isnโ€™t just theory in Ruby โ€” itโ€™s the core of how your Rails app is structured. From models to services, everything is built as objects to make the app reusable, clean, and powerful.

๐Ÿ’Ž Gems and Libraries That Support OOP in Ruby

Ruby and Rails provide built-in support for Object-Oriented Programming, but the following gems and libraries can help you write cleaner, more modular, and testable OOP code:

๐Ÿงฉ Gem / Library๐Ÿ“Œ Purpose / Description
dry-rbA collection of gems (like dry-struct, dry-types, dry-validation) that help you build modular, immutable, and typed objects.
trailblazerHelps you organize business logic into objects like operations, cells, and contracts (ideal for large OOP Rails apps).
reformForm objects gem that keeps validations out of models. Promotes separation of concerns via OOP principles.
interactorEncapsulates a single unit of business logic in a class called an interactor. Encourages clean OOP service objects.
virtus (deprecated)Used to build plain old Ruby objects (POROs) with attributes โ€” replaced largely by dry-struct.
active_model_serializersTurns your models into structured JSON objects โ€” helpful for API object representation.
punditProvides object-oriented policies for authorization logic. Follows OOP patterns with Policy classes.
simple_commandService object helper gem to encapsulate logic in clean command-style objects.
dry-monadsEncourages object-oriented flow control (success/failure) in functional or OOP style.
rspec-railsEncourages OOP-style specs and mocking by testing behavior of objects, not just output.

๐Ÿ“ฆ Example Gemfile Snippet

# Gemfile
gem 'dry-struct'
gem 'reform'
gem 'interactor'
gem 'pundit'
gem 'simple_command'

These libraries are not required to use OOP in Ruby, but they promote better separation of concerns, testing, and modular design โ€” all essential aspects of object-oriented programming.

๐Ÿงฑ Best Implementation of a Class in Ruby (With Real-World Use)

๐Ÿงญ Goal

Build a User class that stores name and email, validates the data, and can greet the user or check if the email is valid.

1๏ธโƒฃ Define the Class and Attributes

class User
  attr_accessor :name, :email

  def initialize(name, email)
    @name = name
    @email = email
  end
  • attr_accessor automatically creates getter/setter methods.
  • initialize is the constructor, called when we create a new object.
  • @name and @email are instance variables.

2๏ธโƒฃ Add Instance Methods (Behavior)

  def greet
    \"Hello, #{@name}!\"
  end

  def valid_email?
    email.include?('@') && email.end_with?('.com')
  end
end
  • greet uses data inside the object.
  • valid_email? checks email formatting.

3๏ธโƒฃ Create and Use the Object

# Creating object
user = User.new(\"Ali\", \"ali@example.com\")

# Calling methods
puts user.greet           # => \"Hello, Ali!\"
puts user.valid_email?    # => true

4๏ธโƒฃ Add Input Validation (Optional)

class User
  attr_accessor :name, :email

  def initialize(name, email)
    raise ArgumentError, \"Name can't be blank\" if name.strip.empty?
    raise ArgumentError, \"Invalid email\" unless email.include?('@')

    @name = name
    @email = email
  end
end

This ensures bad data is caught early.

โœ… Best Practices

  • Use attr_reader / attr_writer for read-only/write-only access when needed.
  • Encapsulate logic inside methods, not in scripts or controllers directly.
  • Keep classes focused on one responsibility (Single Responsibility Principle).
  • Validate input early and raise clear exceptions.
  • Keep instance variables private unless there’s a reason to expose them.

๐ŸŒ Real-World Use Case (Rails)

In Rails, models are classes. Example: User < ApplicationRecord

class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }

  def greet
    \"Welcome, \#{name}\"
  end
end

This keeps your logic organized and your Rails app clean and testable.

๐Ÿ“ฆ Best Implementation of a Module in Ruby (With Real-World Use)

๐Ÿงญ Goal

Create a Sharable module that can be included in any class to provide social media sharing functionality. This promotes code reuse without inheritance.

๐Ÿ“Œ What is a Module?

  • A module is a container for methods and constants.
  • It cannot be instantiated like a class.
  • Modules are used for namespacing or mixing in behavior via include or extend.

1๏ธโƒฃ Define the Module

module Sharable
  def share(platform)
    \"Shared on \#{platform.capitalize} by \#{@name}!\"
  end
end

This module defines one reusable method called share.

2๏ธโƒฃ Include Module in a Class

class User
  include Sharable

  attr_reader :name

  def initialize(name)
    @name = name
  end
end
  • include adds instance methods from the module to the class.
  • User now has access to the share method.

3๏ธโƒฃ Use the Object with Mixed-In Methods

user = User.new(\"Ayesha\")
puts user.share(\"twitter\")  # => \"Shared on Twitter by Ayesha!\"

4๏ธโƒฃ Extend the Module for Class Methods

module Logger
  def log(message)
    puts \"[LOG] \#{message}\"
  end
end

class App
  extend Logger
end

App.log(\"Application started\")  # => [LOG] Application started
  • extend adds module methods as class methods.

๐Ÿง  When to Use a Module

  • To share behavior across unrelated classes
  • To avoid duplication of utility logic
  • To organize related methods together
  • As a lightweight alternative to inheritance

โœ… Best Practices

  • Use include for instance methods and extend for class methods.
  • Group related behavior logically (e.g., Exportable, Sharable, Auditable).
  • Donโ€™t put state (like instance variables) in modules unless you control the context.
  • Use modules for mixins, not for core business logic.
  • Avoid deep module nesting unless you’re building a gem or namespacing APIs.

๐ŸŒ Real-World Use Case (Rails)

Modules are used extensively in Rails:

  • ActiveModel::Validations โ€“ adds validation methods to models
  • Devise::Models โ€“ mixin authentication methods
  • Concern โ€“ Rails DSL for modularizing reusable code in models/controllers

๐Ÿ“ฆ Example: Using Concern in Rails

# app/models/concerns/archivable.rb
module Archivable
  extend ActiveSupport::Concern

  def archive!
    update(archived: true)
  end
end

# app/models/post.rb
class Post < ApplicationRecord
  include Archivable
end

Now every Post can call post.archive!.

๐ŸŽฏ Summary

Modules in Ruby help make your code **modular**, **DRY**, and **extensible** โ€” especially when you want to share functionality without inheritance. They’re essential to both Ruby and Rails design patterns.

๐Ÿ”„ Best Implementation of Mixins in Ruby (With Practical Use)

๐Ÿงญ Goal

Use a **mixin** to add shared functionality (like logging) across multiple classes, without using inheritance.

๐Ÿ“Œ What is a Mixin?

  • A **mixin** in Ruby is when you use a module to add reusable methods to one or more classes.
  • This is done using include (for instance methods) or extend (for class methods).
  • Unlike classes, you can include multiple modules into one class โ€” allowing flexible code reuse.

1๏ธโƒฃ Define a Mixin Module

module LoggerMixin
  def log_action(action)
    puts \"[LOG] \#{action} at \#{Time.now}\"
  end
end

This module defines a reusable method for logging actions.

2๏ธโƒฃ Include It in a Class

class PaymentProcessor
  include LoggerMixin

  def process(amount)
    log_action(\"Processing payment of $#{amount}\")
    # Payment logic...
  end
end

3๏ธโƒฃ Create and Use the Object

processor = PaymentProcessor.new
processor.process(99.99)

# Output:
# [LOG] Processing payment of $99.99 at 2025-05-29 12:00:00

4๏ธโƒฃ Add Another Class Using the Same Mixin

class UserNotifier
  include LoggerMixin

  def send_email(user)
    log_action(\"Sending email to #{user}\")
    # Email logic...
  end
end

notifier = UserNotifier.new
notifier.send_email(\"ayesha@example.com\")

Now both classes reuse the same log_action method!

โœ… Best Practices for Mixins

  • โœ… Use for shared behavior, not shared data.
  • โœ… Group logically related methods together.
  • โœ… Prefer modules over inheritance for reusable logic.
  • โŒ Avoid using instance variables inside modules unless controlled carefully.
  • โœ… Prefix method names uniquely in large apps to avoid clashes.

๐Ÿ“˜ Mixin vs Inheritance

MixinsInheritance
Include multiple modulesOnly one parent class allowed
Flexible & reusableRigid structure
Good for behaviorGood for hierarchy

๐ŸŒ Real-World Rails Example: ActiveSupport::Concern

# app/models/concerns/loggable.rb
module Loggable
  extend ActiveSupport::Concern

  def log_record(message)
    Rails.logger.info \"[LOG] \#{message}\"
  end
end

# app/models/order.rb
class Order < ApplicationRecord
  include Loggable

  after_create { log_record(\"Order ##{id} created\") }
end

Using mixins like this helps separate concerns and keeps models clean.

๐ŸŽฏ Summary

Mixins let you share methods across multiple classes using modules. Theyโ€™re a powerful way to organize and reuse code in Ruby and Rails applications.

๐Ÿงพ Best Way to Handle Attributes in Ruby Classes (With Explanation)

๐Ÿงญ Goal

Define and manage object attributes in a clean, controlled, and Ruby-idiomatic way using attr_reader, attr_writer, and attr_accessor.

1๏ธโƒฃ Basic Attribute Access with attr_accessor

class Product
  attr_accessor :name, :price

  def initialize(name, price)
    @name = name
    @price = price
  end
end

product = Product.new(\"Shoes\", 49.99)
puts product.name       # => Shoes
product.price = 59.99
puts product.price      # => 59.99
  • attr_accessor creates both getter and setter methods.
  • Use it when you want to read and write the value from outside the class.

2๏ธโƒฃ Use attr_reader for Read-Only Attributes

class User
  attr_reader :email

  def initialize(email)
    @email = email
  end
end

user = User.new(\"test@example.com\")
puts user.email         # => test@example.com
# user.email = \"new@example.com\"  โŒ Error: undefined method
  • Protects the attribute from being changed outside the class.
  • Use attr_reader when the value should only be set once (e.g., ID, email).

3๏ธโƒฃ Use attr_writer for Write-Only Attributes (Rare)

class PasswordManager
  attr_writer :password

  def verify(input)
    input == @password
  end
end

manager = PasswordManager.new
manager.password = \"secret123\"
  • Use when a value should be written but not read publicly (e.g., passwords).

4๏ธโƒฃ Custom Setters with Validation or Logic

class Invoice
  attr_reader :amount

  def amount=(value)
    raise \"Amount must be positive\" if value <= 0
    @amount = value
  end
end

invoice = Invoice.new
invoice.amount = 100     # โœ…
invoice.amount = -5      # โŒ raises error
  • Custom setters allow you to add business rules when setting a value.
  • Great for sanitizing, formatting, or validating inputs.

5๏ธโƒฃ Freeze Immutable Attributes

class Config
  attr_reader :mode

  def initialize(mode)
    @mode = mode.freeze
  end
end

Freezing values ensures they can’t be modified after being set.

โœ… Best Practices

  • Use attr_reader for constants and immutable attributes.
  • Use attr_writer sparingly โ€” mostly for sensitive data.
  • Use attr_accessor only when both reading and writing are safe.
  • Prefer custom setters when validation or side effects are needed.
  • Keep instance variables private whenever possible.
  • Document attribute behavior clearly if custom logic is added.

๐ŸŒ Real-World Rails Example

Rails models automatically expose database fields as attributes. But for non-DB attributes:

class Report
  attr_accessor :title

  def initialize(title)
    @title = title
  end

  def printable_title
    title.strip.titleize
  end
end

๐ŸŽฏ Summary

Ruby provides elegant, flexible ways to manage attributes through attr_reader, attr_writer, and attr_accessor. Choose the right tool based on the level of control you need. For public interfaces, always prefer clarity and safety.

๐Ÿงช Examples for All Key OOP Terms and Types in Ruby

๐Ÿงฉ Term / Type๐Ÿ“˜ Example๐Ÿ’ก Explanation
Class
class User
  def greet
    \"Hi!\"
  end
end
Defines a blueprint for objects (e.g., User objects).
Object
user = User.new
puts user.greet
Object created from the class using .new.
Method
def greet
  \"Hello\"
end
Function inside a class defining behavior.
Instance Variable
def initialize(name)
  @name = name
end
Stores data unique to each object, prefixed with @.
attr_accessor
attr_accessor :name
Creates both getter and setter for @name.
attr_reader
attr_reader :id
Creates read-only access for an attribute.
attr_writer
attr_writer :password
Allows only write access (used for sensitive data).
Encapsulation
private

def internal_logic
  # not accessible from outside
end
Keeps methods hidden from public use.
Inheritance
class Admin < User
end
Admin inherits methods and attributes from User.
Polymorphism
def speak
  \"Animal sound\"
end

class Dog < Animal
  def speak
    \"Bark\"
  end
end
Same method name behaves differently in child classes.
Module
module Sharable
  def share
    puts \"Shared!\"
  end
end
A collection of methods to be reused in other classes.
Mixin
class Post
  include Sharable
end
Mixes module behavior into a class using include.
self
def self.all
  # class method
end
self refers to the current object or class context.
super
def initialize(name)
  super(name)
end
Calls the same method in the parent class.
Duck Typing
def print_title(item)
  item.title
end
Focuses on object behavior, not its class type.
Custom Setter
def price=(val)
  raise \"Invalid\" if val < 0
  @price = val
end
Control attribute assignment with validations or transformations.
Freeze Attribute
@name = name.freeze
Makes the value immutable (cannot be changed).
Namespace (Module)
module API
  class User
  end
end
Organizes code and avoids naming conflicts.
Class Variable
@@count = 0
Shared across all instances of the class. Rarely recommended.
Constant
TAX_RATE = 0.18
Immutable and typically used for configuration or constants.

๐Ÿงฉ Different Ways to Pass Arguments and Define Parameters in Ruby

๐Ÿง  Type๐Ÿ“˜ Syntax๐Ÿ’ก Example & Use Case
1. Positional Parametersdef greet(name)
def greet(name)
  \"Hello, #{name}\"
end

puts greet(\"Ali\")
Best for required input values.
2. Default Parametersdef greet(name = \"Guest\")
def greet(name = \"Guest\")
  \"Hello, #{name}\"
end

puts greet()          # => Hello, Guest
puts greet(\"Sara\")   # => Hello, Sara
Allows optional arguments.
3. Variable Number of Argumentsdef sum(*nums)
def sum(*nums)
  nums.sum
end

puts sum(1, 2, 3, 4)   # => 10
Accepts any number of arguments.
4. Keyword Argumentsdef login(username:, password:)
def login(username:, password:)
  \"Logged in as #{username}\"
end

puts login(username: \"admin\", password: \"123\")
Named, required arguments โ€” improves readability.
5. Optional Keyword Argumentsdef welcome(name: \"Guest\")
def welcome(name: \"Guest\")
  \"Welcome, #{name}\"
end

puts welcome()             # => Welcome, Guest
puts welcome(name: \"Zara\")
Clean way to handle optional values by name.
6. Keyword Arguments with Double Splatdef configure(**options)
def configure(**options)
  puts options
end

configure(mode: \"dark\", theme: \"blue\")
Captures all keyword arguments as a hash.
7. Mixing Positional + Keyworddef setup(user, admin: false)
def setup(user, admin: false)
  \"#{user}, Admin: #{admin}\"
end

puts setup(\"Ali\", admin: true)
Useful for flexible interfaces.
8. Block Parametersdef each(&block)
def greet_twice
  yield
  yield
end

greet_twice { puts \"Hello\" }
Pass a block and call it using yield.
9. Proc / Lambda Parametersdef process(action)
say_hi = -> { puts \"Hi\" }

def process(action)
  action.call
end

process(say_hi)
Pass behavior as an object (OOP style).
10. Destructured Parametersdef draw((x, y))
def draw((x, y))
  \"Point: #{x}, #{y}\"
end

puts draw([10, 20])
Useful when working with arrays or coordinates.

๐Ÿง  Mastering these parameter types helps you write clean, flexible, and expressive Ruby methods that scale well in real applications and APIs.

โš–๏ธ Class Variables vs Instance Variables: Use Cases & Examples

AspectInstance Variable @variableClass Variable @@variable
ScopeBelongs to one object (instance)Shared across all instances and the class
Used InInstance methodsBoth instance and class methods
PurposeStores data unique to each objectStores shared data like counters or configs
PersistenceExists only for the life of the objectExists throughout the class lifecycle
InheritanceIndependent per objectShared even across subclasses (can be dangerous)

๐Ÿง Instance Variable Use Cases

  • ๐Ÿ“ User profile with @name, @email
  • ๐Ÿ“ฆ Product with @price, @quantity
  • ๐Ÿ” Per-request data in a web app (like session or token)
class Product
  def initialize(name, price)
    @name = name
    @price = price
  end

  def info
    "#{@name} costs $#{@price}"
  end
end

p1 = Product.new("Book", 10)
p2 = Product.new("Pen", 2)
puts p1.info  # => Book costs $10
puts p2.info  # => Pen costs $2

๐Ÿข Class Variable Use Cases

  • ๐Ÿ”ข Counter to track total instances
  • ๐Ÿšฅ Shared configuration value like API limits
  • ๐Ÿ“Š Aggregated stats across objects
class User
  @@user_count = 0

  def initialize
    @@user_count += 1
  end

  def self.total_users
    @@user_count
  end
end

User.new
User.new
puts User.total_users  # => 2

โš ๏ธ Best Practice

  • โœ… Use @ for per-object state (most common)
  • โš ๏ธ Be cautious with @@ โ€” it’s shared across subclasses and can behave unexpectedly
  • โœ… Prefer @ with class methods (i.e., class instance variable) if you need shared config

๐ŸŽฏ Bonus: Class Instance Variable Example

class Setting
  @config = {}

  def self.set(key, value)
    @config[key] = value
  end

  def self.get(key)
    @config[key]
  end
end

Setting.set(:theme, "dark")
puts Setting.get(:theme)  # => dark

Class instance variables @var in class context are safer than @@var for shared logic, especially in Rails and modern Ruby apps.

๐Ÿ”’ Are @ and @@ Variables Internal Only?

โœ… Yes โ€” They Are Internal to the Class

In Ruby, both:

  • @instance_variable
  • @@class_variable

…are **internal/private to the object or class**. You cannot access them directly from the outside using:

  • User.variable โŒ
  • user.variable โŒ

๐Ÿ› ๏ธ You Need Methods to Expose Them

You can either define your own method or use helpers like:

  • attr_reader โ†’ for read-only
  • attr_writer โ†’ for write-only
  • attr_accessor โ†’ for both

๐Ÿ“Œ Example:

class User
  @@total_users = 10
  @admin = true

  def initialize
    @name = "Ali"
  end

  def name
    @name
  end

  def self.total_users
    @@total_users
  end

  def self.admin
    @admin
  end
end

puts User.total_users  # => 10
puts User.admin        # => true
puts User.new.name     # => Ali

โš ๏ธ Direct Access (Hacking)

User.class_variable_get(:@@total_users)         # => 10
User.instance_variable_get(:@admin)             # => true
User.new.instance_variable_get(:@name)          # => nil unless initialized

โ— But these are not recommended in production apps โ€” they’re for metaprogramming/debugging.

โœ… Conclusion

  • All @ and @@ are private/internal by default.
  • To access them publicly, define getter/setter methods.
  • Using attr_* makes this easier.

๐Ÿง  20 Technical OOP Questions in Ruby with Answers & Examples

  1. Q1: What is a class in Ruby?
    A: A blueprint for creating objects.
    class User
      def greet
        \"Hello!\"
      end
    end
  2. Q2: How do you create an object?
    A: Use .new method on a class.
    user = User.new
    puts user.greet
  3. Q3: What is an instance variable?
    A: A variable tied to a specific object, prefixed with @.
    @name = \"Ali\"
  4. Q4: What does attr_accessor do?
    A: Creates both getter and setter methods.
    attr_accessor :name
  5. Q5: What is encapsulation?
    A: Restricting access to internal methods and data.
    private
    
    def secret
      \"hidden logic\"
    end
  6. Q6: How do you inherit from a parent class?
    A: Use < to inherit.
    class Admin < User
    end
  7. Q7: How do you override a method in a subclass?
    A: Define the method again in the child class.
    def greet
      \"Hi, admin!\"
    end
  8. Q8: What is polymorphism in Ruby?
    A: Different classes can respond to the same method name.
    class Dog; def speak; \"Bark\"; end; end
    class Cat; def speak; \"Meow\"; end; end
  9. Q9: Whatโ€™s the difference between include and extend?
    A: include adds instance methods; extend adds class methods.
  10. Q10: How do you define a module?
    A: Use module keyword.
    module Sharable
      def share
        puts \"Shared!\"
      end
    end
  11. Q11: What is a mixin?
    A: Using a module to add reusable functionality to a class.
    include Sharable
  12. Q12: What is self in Ruby?
    A: Refers to the current object or class context.
    def self.status
      \"Active\"
    end
  13. Q13: What does super do?
    A: Calls the same method in the parent class.
    def initialize(name)
      super(name)
    end
  14. Q14: How do you make an attribute read-only?
    A: Use attr_reader.
    attr_reader :email
  15. Q15: How can you validate attribute data in a setter?
    A: Define a custom setter method.
    def age=(val)
      raise \"Invalid\" if val < 0
      @age = val
    end
  16. Q16: How do you freeze an attribute value?
    A: Use .freeze to make it immutable.
    @mode = mode.freeze
  17. Q17: What is duck typing?
    A: Behavior over type โ€” if it acts like the expected object, itโ€™s accepted.
    def render(page)
      page.render_html
    end
  18. Q18: How do you define a constant in Ruby?
    A: Use all caps. Constants should not change.
    MAX_RETRIES = 3
  19. Q19: What is the difference between @ and @@?
    A: @ is instance-specific; @@ is shared across all instances.
    @name = \"Ali\"
    @@count = 0
  20. Q20: How do you group related classes or modules?
    A: Use namespacing with modules.
    module Admin
      class Dashboard
      end
    end

โœ… OOP in Ruby โ€“ Best Practices with Examples

  1. 1. Use attr_reader Instead of attr_accessor When Mutability Isnโ€™t Needed

    Encapsulation is key. Prevent accidental changes by exposing only whatโ€™s needed.

    class User
      attr_reader :email
    
      def initialize(email)
        # email is immutable
        @email = email
      end
    end
  2. 2. Group Shared Methods in Modules (Mixins)

    Extract reusable behavior using modules and include them where needed.

    module Sharable
      def share
        puts \"Shared!\"
      end
    end
    
    class Post
      include Sharable
    end
  3. 3. Use Custom Setters to Validate Input

    Encapsulate rules inside the object and enforce them at the data level.

    class Invoice
      attr_reader :amount
    
      def amount=(value)
        raise \"Invalid amount\" if value <= 0
        @amount = value
      end
    end
  4. 4. Keep Classes Small โ€“ Follow SRP (Single Responsibility Principle)

    One class should have one purpose. Split logic into separate classes.

    # good
    class OrderProcessor
      def process(order); end
    end
    
    class PaymentGateway
      def charge(card, amount); end
    end
  5. 5. Prefer Composition Over Inheritance When Behavior Varies

    Use helper objects or modules when class hierarchy isnโ€™t clean.

    class Notification
      def initialize(sender)
        @sender = sender
      end
    
      def deliver(message)
        @sender.send_message(message)
      end
    end
  6. 6. Use super Carefully When Overriding Methods

    Make sure to retain base behavior when appropriate.

    class Base
      def greet
        \"Hello\"
      end
    end
    
    class Child < Base
      def greet
        super + \", friend!\"
      end
    end
  7. 7. Avoid Global State โ€“ Use Dependency Injection

    Inject dependencies instead of hardcoding them inside methods.

    class Report
      def initialize(formatter)
        @formatter = formatter
      end
    
      def generate
        @formatter.format(data)
      end
    end
  8. 8. Donโ€™t Overuse Inheritance โ€“ Favor Modules for Behavior

    Ruby allows mixing modules into multiple classes, unlike single-inheritance limitations.

    module Trackable
      def track
        puts \"Tracked action\"
      end
    end
  9. 9. Use initialize for Required Data

    Force required attributes up front for a consistent object state.

    class Product
      def initialize(name, price)
        raise \"Missing info\" unless name && price
        @name = name
        @price = price
      end
    end
  10. 10. Freeze Immutable Attributes for Safety

    Prevent accidental changes to constant-like values.

    @currency = \"USD\".freeze

โœจ Following these OOP best practices helps you write clean, scalable, and testable Ruby code โ€” whether youโ€™re building a small script or a large Rails application.

๐Ÿ” Instance vs Class vs Static Methods & Variables in Ruby

๐Ÿ’ก Type๐Ÿ“˜ Description๐Ÿ“Œ Syntax & Example๐Ÿง  Use Case
Instance Variable
@variable
Data unique to each object instance.
class User
  def initialize(name)
    @name = name
  end

  def greet
    \"Hi, #{@name}\"
  end
end

user1 = User.new(\"Ali\")
puts user1.greet
Used when each object stores its own data.
Instance MethodMethods that operate on instance variables.
def greet
  \"Hello, #{@name}\"
end
Common for business logic tied to a record.
Class Variable
@@variable
Shared across all instances of a class.
class User
  @@count = 0

  def initialize
    @@count += 1
  end

  def self.count
    @@count
  end
end
Used for shared tracking across all instances.
Class Method
self.method
Methods called on the class itself, not an instance.
class MathUtils
  def self.square(x)
    x * x
  end
end

puts MathUtils.square(4)
Used for utility logic or factory methods.
Constant (Static Variable)
CONSTANT
Immutable data shared across all instances.
class Settings
  API_KEY = \"ABC123\"
end

puts Settings::API_KEY
Used for configuration, settings, fixed rules.
Static-style Method (Module)
module.method
Method defined in a module and called without instantiation.
module Greetings
  def self.welcome
    \"Welcome!\"
  end
end

puts Greetings.welcome
Used for utility functions (like helpers).

โœ… Summary

  • @variable โ†’ Per-object state (instance)
  • @@variable โ†’ Shared across instances (class-wide)
  • CONSTANT โ†’ Global static-like values
  • Instance methods โ†’ Called on objects
  • Class methods โ†’ Called on the class
  • Static-like methods โ†’ Called via module or class without instantiation

โ“ Why Do We Use attr_reader, attr_writer, attr_accessor in Ruby?

๐Ÿ” What are attr_* methods?

  • attr_reader โ€“ generates a **getter**
  • attr_writer โ€“ generates a **setter**
  • attr_accessor โ€“ generates both getter and setter

โœ… Why Use Them?

They reduce **boilerplate code** and improve readability. Instead of manually writing getters and setters, you declare them with one line.

# With attr_accessor
class User
  attr_accessor :name
end

# Equivalent to:
class User
  def name
    @name
  end

  def name=(value)
    @name = value
  end
end

๐Ÿค” Can We Go Without It?

Yes, technically you can. Ruby lets you define your own getter and setter methods manually:

class Product
  def price
    @price
  end

  def price=(value)
    @price = value
  end
end

โ›” When You Should NOT Use attr_accessor

  • When you want to control or validate input/output.
  • When you want read-only access โ€“ use attr_reader.
  • When you’re working with sensitive data like passwords โ€“ use attr_writer or custom methods.

๐Ÿง  Best Practice Summary

  • โœ… Use attr_reader for immutable or safe-to-read data.
  • โœ… Use attr_writer for write-only values (e.g., passwords).
  • โœ… Use attr_accessor only if you truly need both read and write access.
  • ๐Ÿšซ Avoid exposing internal state carelessly โ€“ encapsulate when possible.

๐Ÿ” Example: Custom Setter with Validation

class Account
  attr_reader :balance

  def balance=(amount)
    raise \"Invalid\" if amount < 0
    @balance = amount
  end
end

๐Ÿ“Œ In conclusion, while you can manually define getters and setters, using attr_* saves time, improves readability, and encourages clean design โ€” just use them wisely!

๐Ÿงช Deep Dive: attr_ vs Instance/Class Variables and Method Access in Ruby

  1. Q1: Can I create instance variables without attr_*?
    โœ… Yes.
    You can define and use @variable inside methods without using attr_reader, attr_writer, or attr_accessor.
    class User
      def set_name(name)
        @name = name
      end
    
      def greet
        \"Hello, #{@name}\"
      end
    end

    attr_* only creates getter/setter methods. You can use instance variables without it if you access them internally.

  2. Q2: Can I call an instance method from a class method?
    โœ… Yes, but you need an object instance.
    class Demo
      def greet
        \"Hello from instance\"
      end
    
      def self.call_greet
        new.greet  # creates an instance and calls greet
      end
    end

    You canโ€™t directly call an instance method from a class method unless you instantiate the object.

  3. Q3: Is attr_* accessible from any instance method?
    โœ… Yes.
    attr_reader, attr_writer, and attr_accessor define methods that can be called from inside other instance methods:
    class User
      attr_accessor :name
    
      def greeting
        \"Hi, #{name}\"  # same as self.name
      end
    end
  4. Q4: Is @variable accessible in all instance methods?
    โœ… Yes.
    Once defined, @variable is available throughout the object’s lifecycle in all instance methods:
    class Product
      def set_price(price)
        @price = price
      end
    
      def show_price
        \"Price is #{@price}\"
      end
    end
  5. Q5: Can class variables @@variable be accessed in instance methods?
    โœ… Yes.
    But be cautious โ€” they are shared across all instances and subclasses.
    class Counter
      @@count = 0
    
      def initialize
        @@count += 1
      end
    
      def show_count
        @@count
      end
    end
  6. Q6: Can both class and instance methods/variables work without attr_*?
    โœ… Yes.
    You can define everything manually. attr_* is just a helper.
    class Manual
      def name
        @name
      end
    
      def name=(val)
        @name = val
      end
    
      def self.version
        @version ||= \"1.0.0\"
      end
    end

    Summary: You donโ€™t need attr_* unless you want to quickly expose instance variables via methods. All instance/class variables and methods can be defined manually and still work.

๐Ÿง  Final Tip

Use attr_reader for safe, readable access, attr_writer for sensitive inputs, and attr_accessor for convenience โ€” but always prefer encapsulation and validation when needed.

โ“ Can I Access Instance Variables Without attr_* Using Object or Class Methods?

๐Ÿ”น From an Object? โœ… Yes

Instance variables (like @name) are tied to objects. You can access them in any instance method โ€” and call that method on the object โ€” even without attr_reader.

class User
  def initialize(name)
    @name = name
  end

  def greet
    "Hi, #{@name}"
  end
end

user = User.new("Ali")
puts user.greet        # => "Hi, Ali"

๐Ÿ”น From a Class Method? โŒ No

Class methods do NOT have access to instance variables like @name, unless they create an instance:

class User
  def initialize(name)
    @name = name
  end

  def greet
    "Hi, #{@name}"
  end

  def self.say_hi
    user = new("Admin")
    user.greet         # This works because we created an instance
  end
end

puts User.say_hi       # => "Hi, Admin"

โš ๏ธ Important Notes

  • ๐Ÿ’ก attr_* is just syntactic sugar for defining getter/setter methods โ€” itโ€™s not required to use or access instance variables internally.
  • ๐Ÿšซ You can’t do user.@name directly โ€” Ruby does not allow accessing instance variables from outside without a getter method.
  • โœ… You can use instance_variable_get (not recommended for production):
    user.instance_variable_get(:@name)

โœ… Conclusion

You can use and access instance variables without attr_*, but only inside instance methods. If you want to access them from outside the object, you need to define a method (manually or via attr_reader).

๐Ÿง  Can attr_* Work Without Defining @variable?

๐Ÿ” Explanation

When you use attr_accessor :name, Ruby automatically creates:

  • A getter method: def name; @name; end
  • A setter method: def name=(value); @name = value; end

So, even if you never manually define @name, it will be created the moment you assign a value using self.name = "Ali" or object.name = "Ali".

โœ… Example Without Explicit @name

class Person
  attr_accessor :name

  def greet
    "Hello, #{name}"
  end
end

p = Person.new
p.name = "Ali"
puts p.greet      # => "Hello, Ali"

๐Ÿ‘‰ Even though @name is never explicitly defined in the class, the moment you run p.name = "Ali", Ruby internally sets @name = "Ali".

๐Ÿ“Œ Key Point

attr_* creates instance methods that read from or write to the corresponding @variable. Itโ€™s safe to use in your class without defining the variable yourself.

๐Ÿ›‘ Reminder

If you try to access name before setting it, you’ll get nil, because the instance variable @name hasn’t been set yet.

p2 = Person.new
puts p2.name   # => nil

๐ŸŽ“ Summary

  • โœ… You donโ€™t need to write @name manually if you use attr_accessor.
  • โœ… Ruby handles the creation of @name when the setter is called.
  • โœ… You can access name inside any instance method using the getter method name.

๐Ÿ“‚ Real-World Case Studies: OOP in Ruby & Rails (10 Scenarios)

  1. ๐Ÿ›’ E-commerce Cart (Encapsulation)
    Problem: Cart logic (add/remove items, total price) scattered in controllers.
    OOP Solution: Encapsulate cart logic in a Cart class.
    class Cart
      def initialize
        @items = []
      end
    
      def add(product)
        @items << product
      end
    
      def total
        @items.sum(&:price)
      end
    end
  2. ๐Ÿ‘ค User Authentication (Inheritance)
    Problem: Admin and Customer share login logic but differ in permissions.
    OOP Solution: Inherit from a common User base class.
    class User
      def login; end
    end
    
    class Admin < User
      def dashboard; end
    end
  3. ๐Ÿ“จ Notification System (Polymorphism)
    Problem: Need to send notifications via email, SMS, and push.
    OOP Solution: Create polymorphic Notifier interface.
    class EmailNotifier
      def send; puts \"Email\"; end
    end
    
    class SmsNotifier
      def send; puts \"SMS\"; end
    end
  4. ๐Ÿ“ฆ Order Fulfillment (Service Object)
    Problem: Checkout logic is large and messy.
    OOP Solution: Move logic into a class with clear responsibility.
    class CheckoutService
      def initialize(order)
        @order = order
      end
    
      def call
        charge_payment
        update_inventory
      end
    end
  5. ๐Ÿ“ˆ Dashboard Metrics (Presenter Pattern)
    Problem: Complex formatting logic inside views.
    OOP Solution: Add a presenter class to format data.
    class MetricPresenter
      def initialize(metric)
        @metric = metric
      end
    
      def display
        \"#{@metric.name}: #{@metric.value.round(2)}\"
      end
    end
  6. ๐Ÿ“„ Invoice Generation (Composition)
    Problem: PDF generation and tax calculation are coupled.
    OOP Solution: Compose Invoice class with TaxCalculator and PdfGenerator.
    class Invoice
      def initialize(tax_calc, pdf)
        @tax_calc = tax_calc
        @pdf = pdf
      end
    
      def generate
        total = @tax_calc.compute
        @pdf.render(total)
      end
    end
  7. ๐Ÿ” Role-Based Access Control (Modules & Mixins)
    Problem: Common permission checks repeated in many models.
    OOP Solution: Use Permissions module as mixin.
    module Permissions
      def can_edit?
        role == 'admin'
      end
    end
  8. ๐Ÿ“ง Email Formatting (Decorator Pattern)
    Problem: Email content is hard to format dynamically.
    OOP Solution: Wrap email object with decorators.
    class EmailDecorator
      def initialize(email)
        @email = email
      end
    
      def formatted_subject
        \"[#{Time.now.year}] #{@email.subject}\"
      end
    end
  9. ๐Ÿ“š Form Handling (Form Object)
    Problem: Multiple models handled in one form (e.g., user + profile).
    OOP Solution: Create a PORO Form object to validate/submit both.
    class SignupForm
      include ActiveModel::Model
      attr_accessor :user, :profile
    
      def save
        user.save && profile.save
      end
    end
  10. ๐Ÿงพ Payment Gateway Abstraction (Interface Pattern)
    Problem: Switch between Stripe, PayPal, etc., without rewriting code.
    OOP Solution: Create shared interface for all gateways.
    class StripeGateway
      def charge(amount)
        # Stripe logic
      end
    end
    
    class PaypalGateway
      def charge(amount)
        # PayPal logic
      end
    end

๐Ÿง  These scenarios show how OOP isn't just theory โ€” it's used in almost every part of a real-world Ruby or Rails application to improve code clarity, reuse, and maintainability.

Learn more aboutย Railsย setup

10 thoughts on “OOP in Ruby Explained with Easy Examples”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top