CrackedRuby CrackedRuby

Overview

Classes define blueprints for creating objects that combine data and behavior into cohesive units. An object represents a specific instance of a class, containing its own state while sharing structure and behavior defined by the class. This paradigm emerged from Simula in the 1960s and became widespread through Smalltalk, C++, Java, and Ruby.

The class-object relationship provides a template mechanism: classes specify what data objects hold and what operations they perform, while objects represent individual entities with distinct values. A Car class might define attributes like color and model, plus behaviors like start and stop. Each car object maintains its own color and model values while sharing the start and stop implementations.

Ruby treats everything as an object, including classes themselves. Classes are first-class objects, instances of the Class class. This design allows runtime class modification, dynamic method definition, and metaprogramming capabilities that distinguish Ruby from statically-typed languages.

# Class definition
class Book
  def initialize(title, author)
    @title = title
    @author = author
  end
  
  def description
    "#{@title} by #{@author}"
  end
end

# Object creation
book = Book.new("1984", "George Orwell")
book.description
# => "1984 by George Orwell"

Classes organize related functionality, reduce code duplication through inheritance, and provide interfaces that hide implementation details. Objects maintain state across method calls, enabling stateful programming where data persists between operations.

Key Principles

Encapsulation bundles data and methods operating on that data within a single unit, restricting direct access to internal state. Objects expose public interfaces while hiding implementation details. Ruby implements encapsulation through access control keywords: public, private, and protected. Instance variables remain private by default, accessible only through defined methods.

class BankAccount
  def initialize(balance)
    @balance = balance
  end
  
  def deposit(amount)
    @balance += amount if amount > 0
  end
  
  def balance
    @balance
  end
  
  private
  
  def validate_withdrawal(amount)
    amount > 0 && amount <= @balance
  end
end

The @balance instance variable cannot be accessed directly from outside the class. The validate_withdrawal method remains internal to the class implementation, preventing external code from calling it.

Abstraction exposes essential features while hiding unnecessary complexity. Classes define interfaces representing object capabilities without revealing implementation details. Abstract concepts translate to concrete code through class design. A EmailService class might expose a send method without revealing SMTP protocol details, connection pooling, or retry logic.

Identity and State distinguish objects. Identity means each object occupies distinct memory space, even when containing identical data. State comprises the values stored in an object's instance variables at any moment. Objects modify their state through method calls, maintaining data across operations.

account1 = BankAccount.new(1000)
account2 = BankAccount.new(1000)

account1.object_id == account2.object_id
# => false (different objects)

account1.deposit(500)
# account1 state changed, account2 unchanged

Behavior and Methods define operations objects perform. Methods access and modify object state, implement business logic, and interact with other objects. Instance methods operate on specific object instances, accessing instance variables unique to each object. Class methods operate on the class itself, independent of individual instances.

Message Passing describes how objects interact. Calling a method on an object sends a message to that object. The object determines how to handle the message through method lookup. Ruby's dynamic nature allows objects to respond to messages not explicitly defined through method_missing.

class Logger
  def method_missing(method_name, *args)
    puts "[#{method_name.upcase}] #{args.join(' ')}"
  end
end

logger = Logger.new
logger.error("Connection failed")
# => "[ERROR] Connection failed"

Type and Class Membership establish object categories. An object's class determines its type, defining what messages it responds to and what behavior it exhibits. Ruby uses duck typing: if an object responds to required methods, its class matters less than its capabilities. This differs from nominal typing where class hierarchy determines type compatibility.

Class Hierarchy and Inheritance enable code reuse through parent-child relationships. Subclasses inherit methods and behavior from parent classes, overriding or extending functionality. Ruby supports single inheritance where each class has one direct parent, but includes modules for mixin-based multiple inheritance.

Ruby Implementation

Ruby implements classes using the class keyword, defining a namespace for methods and constants. Class names start with capital letters following constant naming conventions. The initialize method serves as the constructor, called automatically when creating new objects through new.

class Product
  attr_reader :name, :price
  attr_accessor :quantity
  
  def initialize(name, price, quantity = 0)
    @name = name
    @price = price
    @quantity = quantity
  end
  
  def total_value
    @price * @quantity
  end
  
  def restock(amount)
    @quantity += amount
  end
end

product = Product.new("Widget", 25.50, 100)
product.total_value
# => 2550.0

Instance Variables store object state, denoted by the @ prefix. Each object maintains separate instance variables, even for objects of the same class. Instance variables don't require declaration and initialize when first assigned. Accessing uninitialized instance variables returns nil without raising errors.

Attribute Accessors provide shorthand for defining getter and setter methods. The attr_reader creates getter methods, attr_writer creates setter methods, and attr_accessor creates both. These macros generate methods at class definition time, eliminating boilerplate code.

class Person
  attr_accessor :name
  attr_reader :birth_year
  
  def initialize(name, birth_year)
    @name = name
    @birth_year = birth_year
  end
  
  def age
    Time.now.year - @birth_year
  end
end

Class Variables and Methods provide shared state and behavior across all instances. Class variables use @@ prefix and maintain single values shared by all objects. Class methods, defined with self.method_name or within class << self blocks, operate on the class rather than instances.

class Counter
  @@count = 0
  
  def initialize
    @@count += 1
  end
  
  def self.total_count
    @@count
  end
end

Counter.new
Counter.new
Counter.total_count
# => 2

Class variables present challenges in inheritance hierarchies, as subclasses share the same variable with parent classes. Class instance variables, stored as instance variables of the class object itself, provide alternatives for class-level state without sharing concerns.

class Tracker
  @items = []
  
  class << self
    attr_accessor :items
  end
  
  def self.add(item)
    @items << item
  end
end

Inheritance extends class functionality through the < operator. Subclasses inherit all methods from parent classes, adding or overriding behavior. The super keyword calls the parent class method with the same name, enabling method extension rather than complete replacement.

class Vehicle
  def initialize(make, model)
    @make = make
    @model = model
  end
  
  def description
    "#{@make} #{@model}"
  end
end

class Car < Vehicle
  def initialize(make, model, doors)
    super(make, model)
    @doors = doors
  end
  
  def description
    "#{super} with #{@doors} doors"
  end
end

car = Car.new("Toyota", "Camry", 4)
car.description
# => "Toyota Camry with 4 doors"

Modules and Mixins provide multiple inheritance capabilities through composition. Modules define reusable functionality included in classes through include (for instance methods) or extend (for class methods). Modules appear in the method lookup chain between a class and its parent.

module Loggable
  def log(message)
    puts "[#{self.class}] #{message}"
  end
end

class Service
  include Loggable
  
  def perform
    log("Starting service")
    # service logic
    log("Service completed")
  end
end

Access Control restricts method visibility. Public methods form the class interface, callable from anywhere. Private methods remain callable only within the class, not even with explicit receivers. Protected methods allow calls from instances of the same class or subclasses.

class Account
  def initialize(balance)
    @balance = balance
  end
  
  def transfer(amount, target)
    return false unless sufficient_funds?(amount)
    withdraw(amount)
    target.deposit(amount)
    true
  end
  
  protected
  
  def deposit(amount)
    @balance += amount
  end
  
  private
  
  def withdraw(amount)
    @balance -= amount
  end
  
  def sufficient_funds?(amount)
    @balance >= amount
  end
end

Singleton Methods define behavior specific to individual objects rather than all instances. These methods exist only on particular objects, created through def object.method_name syntax or using define_singleton_method.

str = "hello"

def str.shout
  upcase + "!!!"
end

str.shout
# => "HELLO!!!"

"hello".shout
# => NoMethodError

Method Visibility and Refinements control method scope and modifications. Refinements limit monkey patching scope through lexically-scoped modifications, preventing global class modifications from affecting unrelated code.

Practical Examples

Domain Modeling with Classes translates real-world concepts into code structures. Consider an e-commerce order system requiring products, orders, and customers. Classes model each entity with appropriate state and behavior.

class Customer
  attr_reader :id, :email, :orders
  
  def initialize(id, email)
    @id = id
    @email = email
    @orders = []
  end
  
  def place_order(order)
    @orders << order
    order.customer = self
  end
  
  def total_spent
    @orders.sum(&:total)
  end
end

class Order
  attr_accessor :customer
  attr_reader :id, :items, :created_at
  
  def initialize(id)
    @id = id
    @items = []
    @created_at = Time.now
  end
  
  def add_item(product, quantity)
    @items << OrderItem.new(product, quantity)
  end
  
  def total
    @items.sum(&:subtotal)
  end
  
  def item_count
    @items.sum(&:quantity)
  end
end

class OrderItem
  attr_reader :product, :quantity
  
  def initialize(product, quantity)
    @product = product
    @quantity = quantity
  end
  
  def subtotal
    @product.price * @quantity
  end
end

# Usage
customer = Customer.new(1, "user@example.com")
order = Order.new(101)
order.add_item(Product.new("Widget", 10.00), 3)
order.add_item(Product.new("Gadget", 25.00), 2)
customer.place_order(order)

customer.total_spent
# => 80.0

This design separates concerns: customers manage order history, orders coordinate items, and order items calculate subtotals. Each class maintains focused responsibility.

State Machines with Objects model entities transitioning between defined states. A document workflow system demonstrates state-dependent behavior where valid operations depend on current state.

class Document
  attr_reader :state, :content
  
  STATES = [:draft, :review, :approved, :published, :archived]
  
  def initialize(content)
    @content = content
    @state = :draft
    @history = []
  end
  
  def submit_for_review
    return false unless @state == :draft
    transition_to(:review)
  end
  
  def approve
    return false unless @state == :review
    transition_to(:approved)
  end
  
  def publish
    return false unless @state == :approved
    transition_to(:published)
  end
  
  def archive
    return false unless [:published, :approved].include?(@state)
    transition_to(:archived)
  end
  
  def reject
    return false unless @state == :review
    transition_to(:draft)
  end
  
  private
  
  def transition_to(new_state)
    @history << { from: @state, to: new_state, at: Time.now }
    @state = new_state
    true
  end
end

Objects maintain state and enforce valid transitions, preventing invalid operations. The state determines available operations, encapsulating workflow rules within the class.

Builder Pattern for Complex Construction addresses objects requiring numerous parameters or complex initialization. The builder provides fluent interface for step-by-step construction.

class QueryBuilder
  def initialize(table)
    @table = table
    @conditions = []
    @order = nil
    @limit = nil
  end
  
  def where(condition)
    @conditions << condition
    self
  end
  
  def order_by(column, direction = :asc)
    @order = { column: column, direction: direction }
    self
  end
  
  def limit(count)
    @limit = count
    self
  end
  
  def to_sql
    sql = "SELECT * FROM #{@table}"
    sql += " WHERE #{@conditions.join(' AND ')}" unless @conditions.empty?
    sql += " ORDER BY #{@order[:column]} #{@order[:direction].to_s.upcase}" if @order
    sql += " LIMIT #{@limit}" if @limit
    sql
  end
end

query = QueryBuilder.new(:users)
  .where("age >= 18")
  .where("status = 'active'")
  .order_by(:created_at, :desc)
  .limit(10)
  .to_sql
# => "SELECT * FROM users WHERE age >= 18 AND status = 'active' ORDER BY created_at DESC LIMIT 10"

Returning self from methods enables method chaining, constructing complex objects through readable sequential calls.

Repository Pattern for Data Access separates domain logic from persistence concerns. Repository classes encapsulate data access operations, providing clean interfaces for storing and retrieving objects.

class UserRepository
  def initialize(database)
    @database = database
  end
  
  def find(id)
    data = @database.query("SELECT * FROM users WHERE id = ?", id).first
    return nil unless data
    build_user(data)
  end
  
  def find_by_email(email)
    data = @database.query("SELECT * FROM users WHERE email = ?", email).first
    return nil unless data
    build_user(data)
  end
  
  def save(user)
    if user.id
      update(user)
    else
      insert(user)
    end
  end
  
  def all
    @database.query("SELECT * FROM users").map { |data| build_user(data) }
  end
  
  private
  
  def build_user(data)
    User.new(
      id: data['id'],
      email: data['email'],
      name: data['name']
    )
  end
  
  def insert(user)
    result = @database.execute(
      "INSERT INTO users (email, name) VALUES (?, ?)",
      user.email, user.name
    )
    user.id = result.last_insert_id
    user
  end
  
  def update(user)
    @database.execute(
      "UPDATE users SET email = ?, name = ? WHERE id = ?",
      user.email, user.name, user.id
    )
    user
  end
end

Repositories centralize data access logic, simplifying testing through mock database injection and enabling database technology changes without affecting business logic.

Design Considerations

When Classes Provide Value depends on problem characteristics. Classes excel at modeling entities with identity, state, and behavior. Systems requiring object lifecycle management, state transitions, or polymorphic behavior benefit from class-based design. Complex domains with multiple related concepts gain clarity through classes representing each concept.

Objects containing mutable state that changes over time suit class-based modeling. Shopping carts, user sessions, and game characters maintain changing state across operations. Classes encapsulate state modifications behind methods, preventing invalid state transitions.

Functional Alternatives exist when operations dominate data. Pure functions operating on immutable data structures avoid state management complexity. Problems decomposing into transformations on data pipelines often prefer functional approaches over object-oriented designs.

# Object-oriented approach
class Calculator
  def initialize(value)
    @value = value
  end
  
  def add(n)
    @value += n
    self
  end
  
  def multiply(n)
    @value *= n
    self
  end
  
  def result
    @value
  end
end

# Functional approach
def add(value, n)
  value + n
end

def multiply(value, n)
  value * n
end

result = multiply(add(10, 5), 2)
# => 30

Functional approaches eliminate state management but sacrifice object identity and encapsulation benefits. Choose based on whether identity and state management provide value for the problem.

Struct and OpenStruct Alternatives offer lightweight options for simple data containers. Structs define classes with attributes without writing full class definitions, suitable for value objects without complex behavior.

Customer = Struct.new(:name, :email, :age) do
  def adult?
    age >= 18
  end
end

customer = Customer.new("Alice", "alice@example.com", 25)
customer.adult?
# => true

OpenStruct provides dynamic attribute assignment without predefining structure, useful for configuration objects or test doubles. OpenStruct trades performance and safety for flexibility.

Class Granularity Trade-offs balance between large monolithic classes and excessive fragmentation. Large classes violate single responsibility but avoid coordination complexity. Small focused classes provide flexibility at the cost of increased file count and navigation difficulty.

Extract classes when responsibilities cluster naturally or when classes exceed comprehension size. Methods operating on distinct subsets of instance variables suggest separate classes. Classes with multiple reasons to change indicate missing abstractions.

Inheritance Versus Composition represents fundamental design choices. Inheritance creates "is-a" relationships, composition creates "has-a" relationships. Inheritance couples subclasses to parent implementation details, making changes risky. Composition provides flexibility through object replacement.

# Inheritance approach
class Animal
  def move
    puts "Moving"
  end
end

class Bird < Animal
  def fly
    puts "Flying"
  end
end

# Composition approach
class Bird
  def initialize
    @movement = Flying.new
  end
  
  def move
    @movement.execute
  end
end

class Flying
  def execute
    puts "Flying"
  end
end

Prefer composition over inheritance except when modeling genuine hierarchical relationships. Composition enables runtime behavior changes and avoids fragile base class problems.

Module Mixins Versus Inheritance provide different code reuse mechanisms. Modules offer multiple inheritance capabilities without commitment to class hierarchies. Include modules for shared behavior across unrelated classes.

module Timestampable
  def touch
    @updated_at = Time.now
  end
  
  def updated_at
    @updated_at
  end
end

class Post
  include Timestampable
  # Post-specific code
end

class Comment
  include Timestampable
  # Comment-specific code
end

Modules work well for horizontal concerns cutting across class hierarchies. Use inheritance for vertical specialization within conceptual hierarchies.

Common Patterns

Factory Pattern centralizes object creation logic, particularly when instantiation requires configuration or decision logic. Factories decouple client code from concrete classes, enabling polymorphism and testing flexibility.

class ReportFactory
  def self.create(type, data)
    case type
    when :pdf
      PdfReport.new(data)
    when :csv
      CsvReport.new(data)
    when :html
      HtmlReport.new(data)
    else
      raise ArgumentError, "Unknown report type: #{type}"
    end
  end
end

report = ReportFactory.create(:pdf, sales_data)
report.generate

Factories isolate creation complexity from usage, allowing creation logic changes without affecting consumers. Abstract factories extend this pattern for creating families of related objects.

Singleton Pattern restricts class instantiation to single shared instances. Ruby implements singletons through the Singleton module, ensuring only one instance exists throughout program execution.

require 'singleton'

class Configuration
  include Singleton
  
  attr_accessor :api_key, :endpoint
  
  def initialize
    @api_key = nil
    @endpoint = "https://api.example.com"
  end
end

config = Configuration.instance
config.api_key = "secret123"

# Same instance everywhere
Configuration.instance.api_key
# => "secret123"

Singletons suit configuration, logging, and connection pooling where multiple instances create problems. However, singletons complicate testing through global state and tight coupling.

Template Method Pattern defines algorithm skeletons in base classes while subclasses implement specific steps. Base classes control flow, subclasses customize behavior at defined extension points.

class DataImporter
  def import(file)
    data = read_file(file)
    parsed = parse_data(data)
    validated = validate_data(parsed)
    save_data(validated)
  end
  
  def read_file(file)
    File.read(file)
  end
  
  def validate_data(data)
    data.select { |row| row_valid?(row) }
  end
  
  def row_valid?(row)
    !row.empty?
  end
  
  def parse_data(data)
    raise NotImplementedError, "Subclass must implement parse_data"
  end
  
  def save_data(data)
    raise NotImplementedError, "Subclass must implement save_data"
  end
end

class CsvImporter < DataImporter
  def parse_data(data)
    CSV.parse(data, headers: true)
  end
  
  def save_data(data)
    data.each { |row| Database.insert(row) }
  end
end

Template methods establish consistent processing flows while allowing customization. This pattern reduces code duplication across similar algorithms.

Observer Pattern establishes one-to-many dependencies where state changes in one object trigger notifications to dependent objects. Observers register interest in subject state changes, receiving updates automatically.

class Subject
  def initialize
    @observers = []
  end
  
  def attach(observer)
    @observers << observer
  end
  
  def detach(observer)
    @observers.delete(observer)
  end
  
  def notify(event)
    @observers.each { |observer| observer.update(event) }
  end
end

class StockPrice < Subject
  attr_reader :symbol, :price
  
  def initialize(symbol, price)
    super()
    @symbol = symbol
    @price = price
  end
  
  def price=(new_price)
    old_price = @price
    @price = new_price
    notify({ symbol: @symbol, old: old_price, new: new_price })
  end
end

class PriceAlert
  def initialize(threshold)
    @threshold = threshold
  end
  
  def update(event)
    if event[:new] > @threshold
      puts "Alert: #{event[:symbol]} exceeded #{@threshold}"
    end
  end
end

stock = StockPrice.new("ACME", 100)
alert = PriceAlert.new(105)
stock.attach(alert)
stock.price = 110
# => "Alert: ACME exceeded 105"

Observers decouple subjects from dependent objects, enabling dynamic subscription management and flexible notification mechanisms.

Decorator Pattern adds responsibilities to objects dynamically without affecting other instances. Decorators wrap objects, forwarding requests while adding behavior before or after delegation.

class Text
  attr_reader :content
  
  def initialize(content)
    @content = content
  end
  
  def render
    @content
  end
end

class BoldDecorator
  def initialize(text)
    @text = text
  end
  
  def render
    "<b>#{@text.render}</b>"
  end
end

class ItalicDecorator
  def initialize(text)
    @text = text
  end
  
  def render
    "<i>#{@text.render}</i>"
  end
end

text = Text.new("Hello")
bold_text = BoldDecorator.new(text)
italic_bold_text = ItalicDecorator.new(bold_text)

italic_bold_text.render
# => "<i><b>Hello</b></i>"

Decorators compose behavior at runtime, avoiding subclass explosion from combining features through inheritance. Each decorator focuses on single concerns.

Strategy Pattern encapsulates algorithms in separate classes, making them interchangeable. Contexts use strategies through common interfaces, enabling runtime algorithm selection.

class Context
  attr_writer :strategy
  
  def execute(data)
    @strategy.process(data)
  end
end

class QuickSort
  def process(data)
    # quicksort implementation
    data.sort
  end
end

class MergeSort
  def process(data)
    # mergesort implementation
    data.sort
  end
end

context = Context.new
context.strategy = QuickSort.new
context.execute([3, 1, 2])

# Switch strategy at runtime
context.strategy = MergeSort.new
context.execute([3, 1, 2])

Strategies eliminate conditional logic for algorithm selection, isolating algorithm variations in separate classes. This pattern supports open-closed principle: extending algorithms without modifying existing code.

Reference

Class Definition Syntax

Syntax Purpose Example
class ClassName Define new class class User
class Child < Parent Define subclass class Admin < User
initialize Constructor method def initialize(name)
self Reference current instance self.update
super Call parent method super(args)
class << self Open eigenclass class << self; def method; end; end

Instance Variables and Attributes

Feature Syntax Description
Instance variable @variable Object-specific state
Class variable @@variable Shared across all instances
attr_reader attr_reader :name Generate getter method
attr_writer attr_writer :name Generate setter method
attr_accessor attr_accessor :name Generate getter and setter

Access Control

Modifier Visibility Callable From
public Everywhere Any code
protected Within class hierarchy Same class and subclasses
private Within class only Only without explicit receiver

Common Class Methods

Method Purpose Returns
new Create instance New object
allocate Create uninitialized instance New object
superclass Get parent class Class or nil
ancestors Get inheritance chain Array of modules and classes
instance_methods List instance methods Array of symbols
class_variables List class variables Array of symbols

Object Methods

Method Purpose Example
class Get object's class obj.class
instance_of? Check exact class obj.instance_of?(String)
is_a? or kind_of? Check class hierarchy obj.is_a?(Numeric)
respond_to? Check method availability obj.respond_to?(:to_s)
object_id Get unique identifier obj.object_id
freeze Make immutable obj.freeze
frozen? Check if frozen obj.frozen?

Method Definition Options

Pattern Description Usage
def method(arg) Instance method Regular methods
def self.method(arg) Class method Called on class itself
def method(arg = default) Default parameter Optional arguments
def method(*args) Variable arguments Accept any number of arguments
def method(arg:) Keyword argument required Named parameters
def method(arg: default) Keyword argument optional Named with defaults
def method(**kwargs) Keyword argument collection Capture all keyword args

Inheritance Concepts

Concept Description Consideration
Single inheritance One parent class Ruby supports only single
Method override Replace parent method Use same method name
Method extension Enhance parent method Call super then add behavior
Abstract methods Unimplemented in parent Raise NotImplementedError
Hook methods Callbacks for events inherited, included, extended

Module Inclusion

Method Effect Use Case
include Add as ancestors Instance methods
prepend Add before class in chain Override instance methods
extend Add to eigenclass Class methods or singleton methods

Design Guidelines

Principle Description Application
Single responsibility One reason to change Focused class purpose
Open-closed Open for extension, closed for modification Use inheritance and composition
Liskov substitution Subclasses replaceable for parents Maintain interface contracts
Interface segregation Many specific interfaces over general Prefer small focused modules
Dependency inversion Depend on abstractions Use duck typing and interfaces

Common Method Patterns

Pattern Code Structure Purpose
Query method def status? Return boolean
Command method def update Perform action
Factory method def self.create Object creation
Builder method def with_option; self; end Fluent interface
Template method def process; step1; step2; end Algorithm skeleton

Class Relationships

Relationship Implementation Example
Inheritance class Child < Parent User < Person
Composition Has instance variable Engine in Car
Aggregation References other objects Students in Course
Association Knows about other class Order references Customer
Dependency Uses another class Controller uses Service