CrackedRuby logo

CrackedRuby

inherited

Overview

Ruby calls the inherited class method automatically whenever a class is subclassed. This callback mechanism allows parent classes to execute code when child classes are created, making it a fundamental building block for metaprogramming frameworks and libraries that need to track or modify subclass behavior.

The inherited method receives one argument: the newly created subclass. Ruby calls this method immediately after the subclass is defined but before any methods or constants are added to the subclass body.

class BaseModel
  def self.inherited(subclass)
    puts "#{subclass} inherits from #{self}"
  end
end

class User < BaseModel
end
# => User inherits from BaseModel

Ruby defines inherited as a class method on Class, with a default empty implementation. Classes override this method to implement custom inheritance behavior. The callback executes within the context of the parent class, not the subclass.

class Framework
  @registered_models = []
  
  def self.inherited(subclass)
    @registered_models << subclass
  end
  
  def self.registered_models
    @registered_models
  end
end

class Product < Framework
end

class Order < Framework  
end

Framework.registered_models
# => [Product, Order]

Multiple inheritance levels each trigger their own inherited callbacks. When a class inherits from a class that itself inherits from another class, Ruby calls inherited on each parent class in the inheritance chain.

class A
  def self.inherited(subclass)
    puts "A: inherited by #{subclass}"
  end
end

class B < A
  def self.inherited(subclass) 
    puts "B: inherited by #{subclass}"
    super
  end
end

class C < B
end
# => B: inherited by #{subclass}
# => A: inherited by #{subclass}

Basic Usage

The most common pattern involves registering subclasses for later processing or validation. Many Ruby frameworks use this approach to maintain registries of model classes, controller classes, or plugin classes without requiring explicit registration calls.

class ActiveRecord
  @models = {}
  
  def self.inherited(subclass)
    @models[subclass.name] = subclass
    subclass.instance_variable_set(:@table_name, subclass.name.downcase + 's')
  end
  
  def self.models
    @models
  end
  
  def self.table_name
    @table_name
  end
end

class User < ActiveRecord
end

class Article < ActiveRecord  
end

ActiveRecord.models
# => {"User"=>User, "Article"=>Article}

User.table_name
# => "users"

Class-level configuration often uses inherited to copy parent class settings to child classes. This ensures that subclasses inherit not just methods but also configuration state from their parents.

class Configurable
  def self.inherited(subclass)
    # Copy parent's configuration to child
    parent_config = instance_variable_get(:@config) || {}
    subclass.instance_variable_set(:@config, parent_config.dup)
  end
  
  def self.configure(key, value)
    @config ||= {}
    @config[key] = value
  end
  
  def self.config
    @config || {}
  end
end

class WebService < Configurable
  configure :timeout, 30
  configure :retries, 3
end

class PaymentService < WebService
  configure :timeout, 60  # Override parent setting
end

WebService.config
# => {:timeout=>30, :retries=>3}

PaymentService.config  
# => {:timeout=>60, :retries=>3}

Validation and constraint enforcement represents another common usage pattern. Parent classes can establish rules that all subclasses must follow, throwing errors during class definition if requirements are not met.

class RestrictedBase
  REQUIRED_METHODS = [:process, :validate]
  
  def self.inherited(subclass)
    # Check will happen after subclass definition completes
    TracePoint.new(:end) do |tp|
      if tp.self == subclass
        missing_methods = REQUIRED_METHODS - subclass.instance_methods
        if missing_methods.any?
          raise "#{subclass} must implement: #{missing_methods.join(', ')}"
        end
        tp.disable
      end
    end.enable
  end
end

class ValidWorker < RestrictedBase
  def process; end
  def validate; end
end

# This would raise an error:
# class InvalidWorker < RestrictedBase
# end

Automatic method generation based on class names or other characteristics frequently relies on inherited callbacks. This pattern allows frameworks to create methods dynamically as classes are defined.

class AutoAccessor
  def self.inherited(subclass)
    # Generate getter and setter for class name
    attr_name = subclass.name.downcase.to_sym
    
    subclass.define_singleton_method("#{attr_name}_count") do
      @count ||= 0
    end
    
    subclass.define_singleton_method("increment_#{attr_name}") do
      @count = (@count || 0) + 1
    end
  end
end

class Order < AutoAccessor
end

Order.order_count
# => 0

Order.increment_order
Order.order_count  
# => 1

Advanced Usage

Complex frameworks often implement inheritance chains where multiple inherited callbacks must coordinate their behavior. Proper implementation requires careful attention to method resolution order and explicit super calls to maintain the inheritance chain.

class FrameworkBase
  def self.inherited(subclass)
    puts "FrameworkBase processing #{subclass}"
    subclass.instance_variable_set(:@framework_initialized, true)
  end
end

class DatabaseMixin
  def self.included(base)
    def base.inherited(subclass)
      puts "DatabaseMixin processing #{subclass}"
      subclass.setup_database_connection
      super if defined?(super)
    end
  end
  
  def self.setup_database_connection
    @db_connection = "Connected to database"
  end
end

class ValidationMixin  
  def self.included(base)
    def base.inherited(subclass)
      puts "ValidationMixin processing #{subclass}"
      subclass.instance_variable_set(:@validations, [])
      super if defined?(super)
    end
  end
end

class ApplicationModel < FrameworkBase
  include DatabaseMixin
  include ValidationMixin
end

class User < ApplicationModel
end
# => ValidationMixin processing User
# => DatabaseMixin processing User  
# => FrameworkBase processing User

Conditional behavior based on subclass characteristics enables sophisticated framework patterns. The inherited callback can inspect the subclass definition and modify behavior accordingly.

class SmartModel
  def self.inherited(subclass)
    # Analyze subclass name and setup accordingly
    class_name = subclass.name
    
    if class_name.end_with?('Controller')
      subclass.extend(ControllerMethods)
      subclass.setup_routing
    elsif class_name.end_with?('Model')  
      subclass.extend(ModelMethods)
      subclass.setup_database_mapping
    elsif class_name.end_with?('Service')
      subclass.extend(ServiceMethods)
      subclass.setup_service_layer
    end
  end
  
  module ControllerMethods
    def setup_routing
      @routes = []
      puts "Routing setup for #{self}"
    end
  end
  
  module ModelMethods
    def setup_database_mapping
      @table_mapping = true
      puts "Database mapping for #{self}"
    end
  end
  
  module ServiceMethods  
    def setup_service_layer
      @service_layer = true
      puts "Service layer for #{self}"
    end
  end
end

class UserController < SmartModel
end
# => Routing setup for UserController

class ProductModel < SmartModel  
end
# => Database mapping for ProductModel

Lazy evaluation patterns use inherited to defer expensive operations until subclasses are actually created. This approach improves application startup time by avoiding unnecessary work during initial loading.

class LazyProcessor
  def self.inherited(subclass)
    # Register for later processing instead of immediate setup
    @pending_subclasses ||= []
    @pending_subclasses << subclass
    
    # Setup lazy initialization
    subclass.define_singleton_method(:process_queue) do
      @processed ||= begin
        puts "Processing queue for #{subclass}"
        expensive_setup_operation
        true
      end
    end
  end
  
  def self.finalize_all_subclasses
    @pending_subclasses&.each do |subclass|
      subclass.process_queue
    end
    @pending_subclasses = []
  end
  
  private
  
  def self.expensive_setup_operation
    # Simulate expensive work
    sleep(0.1)
    "Setup complete"
  end
end

class WorkerA < LazyProcessor
end

class WorkerB < LazyProcessor
end

# Process all at once when ready
LazyProcessor.finalize_all_subclasses
# => Processing queue for WorkerA
# => Processing queue for WorkerB

Dynamic class modification based on inheritance hierarchy allows frameworks to build complex behavior trees. Subclasses can inherit and extend capabilities from multiple ancestors through coordinated inherited callbacks.

class CapabilityFramework
  @capabilities = {}
  
  def self.inherited(subclass)
    # Copy parent capabilities
    parent_capabilities = @capabilities.dup
    subclass.instance_variable_set(:@capabilities, parent_capabilities)
    
    # Allow subclass to declare new capabilities
    def subclass.add_capability(name, &block)
      @capabilities[name] = block
    end
    
    def subclass.capabilities
      @capabilities.keys
    end
    
    def subclass.execute_capability(name, *args)
      capability = @capabilities[name]
      return nil unless capability
      instance_exec(*args, &capability)
    end
  end
end

class BaseService < CapabilityFramework
  add_capability(:logging) { |msg| puts "LOG: #{msg}" }
  add_capability(:timing) { |&block| start = Time.now; result = block.call; puts "Elapsed: #{Time.now - start}"; result }
end

class PaymentService < BaseService
  add_capability(:encryption) { |data| "ENCRYPTED: #{data}" }
end

payment_service = PaymentService.new
payment_service.execute_capability(:logging, "Payment processed")
# => LOG: Payment processed

PaymentService.capabilities
# => [:logging, :timing, :encryption]

Common Pitfalls

The timing of inherited callbacks creates subtle bugs when subclass code expects the callback to have completed. Ruby calls inherited immediately after creating the subclass constant but before processing the class body, which means methods and constants defined in the subclass are not yet available.

class ProblematicBase
  def self.inherited(subclass)
    # This will fail - subclass body hasn't been processed yet
    begin
      subclass.required_method
    rescue NoMethodError
      puts "Method not yet defined during inherited callback"
    end
    
    # This will also be nil - constants not yet defined
    puts "Constant value: #{subclass.const_get(:REQUIRED_CONSTANT) rescue 'Not found'}"
  end
end

class ProblematicChild < ProblematicBase
  REQUIRED_CONSTANT = "Expected Value"
  
  def self.required_method
    "This method exists"
  end
end
# => Method not yet defined during inherited callback
# => Constant value: Not found

# But after class definition completes:
ProblematicChild.required_method
# => "This method exists"
ProblematicChild::REQUIRED_CONSTANT  
# => "Expected Value"

Multiple inheritance callbacks can interfere with each other when they modify the same subclass state. Without careful coordination, later callbacks may overwrite changes made by earlier ones, leading to inconsistent behavior.

class ConflictingBase
  def self.inherited(subclass)
    subclass.instance_variable_set(:@config, {base: "original"})
  end
end

class ConflictingMixin
  def self.included(base)
    def base.inherited(subclass)
      # This overwrites the config set by ConflictingBase
      subclass.instance_variable_set(:@config, {mixin: "replacement"})
      super if defined?(super)
    end
  end
end

class Combined < ConflictingBase
  include ConflictingMixin
end

class TestClass < Combined  
end

# Config from ConflictingBase is lost
TestClass.instance_variable_get(:@config)
# => {:mixin=>"replacement"}

Infinite recursion occurs when inherited callbacks create new subclasses or when method definitions within callbacks trigger additional inheritance events. This pattern can crash applications with stack overflow errors.

class RecursivelyProblematic
  @depth = 0
  
  def self.inherited(subclass)
    @depth += 1
    
    if @depth < 3
      # Creating subclasses within inherited callback
      eval("class Generated#{@depth} < #{subclass}; end")
    end
  end
end

# This creates a cascade of subclass creation
# class TestRecursive < RecursivelyProblematic
# end
# Creates Generated1 < TestRecursive, Generated2 < Generated1, etc.

Memory leaks develop when inherited callbacks store references to subclasses in class variables or constants without providing cleanup mechanisms. Long-running applications can accumulate thousands of class references that prevent garbage collection.

class LeakyRegistry
  @all_subclasses = []
  
  def self.inherited(subclass)
    @all_subclasses << subclass
    # No mechanism to remove classes that are no longer needed
  end
  
  def self.subclass_count
    @all_subclasses.length
  end
end

# In a dynamic environment where classes are created and discarded:
1000.times do |i|
  eval("class DynamicClass#{i} < LeakyRegistry; end")
end

LeakyRegistry.subclass_count
# => 1000

# Classes remain referenced even if no longer used elsewhere
# Memory usage continues to grow over time

Threading issues emerge when inherited callbacks modify shared state without proper synchronization. Multiple threads creating subclasses simultaneously can corrupt class registries or configuration data.

class ThreadUnsafeRegistry
  @registry = {}
  @counter = 0
  
  def self.inherited(subclass)
    # Race condition: multiple threads can read same counter value
    @counter += 1
    @registry[subclass] = @counter
    
    # Simulate some processing time
    sleep(0.001)
  end
  
  def self.registry
    @registry
  end
end

# Multiple threads creating subclasses concurrently
threads = 5.times.map do |i|
  Thread.new do
    eval("class ThreadTest#{i} < ThreadUnsafeRegistry; end")
  end
end

threads.each(&:join)

# Registry may have duplicate counter values due to race conditions
ThreadUnsafeRegistry.registry.values.uniq.length
# May be less than 5 due to race conditions

Reference

Core Method

Method Parameters Returns Description
Class.inherited(subclass) subclass (Class) nil Called when class is subclassed

Callback Timing

Event Timing Subclass State
inherited called Immediately after subclass constant creation Empty class body
Class body processed After inherited completes Methods and constants available
Subclass ready After entire class definition Fully initialized

Method Resolution

Scenario Behavior
Single inheritance Parent's inherited called
Multiple inheritance levels Each parent's inherited called (bottom-up)
Module inclusion Module's inherited hook (if defined) called
Anonymous classes inherited called with anonymous class object

Common Implementation Patterns

# Basic registration
def self.inherited(subclass)
  @subclasses ||= []
  @subclasses << subclass
end

# Configuration copying
def self.inherited(subclass)
  parent_config = @config&.dup || {}
  subclass.instance_variable_set(:@config, parent_config)
end

# Chained callbacks
def self.inherited(subclass)
  # Custom logic here
  super if defined?(super)
end

# Conditional processing
def self.inherited(subclass)
  return if subclass.name.nil? # Skip anonymous classes
  process_named_subclass(subclass)
end

Threading Considerations

Operation Thread Safety Recommendation
Reading class variables Generally safe Use without synchronization
Writing class variables Not thread-safe Use Mutex for protection
Array/Hash modification Not thread-safe Use Concurrent::Array or Mutex
Complex state changes Depends on operations Prefer immutable updates

Performance Impact

Usage Pattern Performance Cost Mitigation
Simple registration Minimal None needed
Complex object creation Moderate Lazy initialization
Extensive metaprogramming High Cache generated methods
Deep inheritance chains Variable Optimize super calls

Error Handling

Error Type Cause Prevention
NoMethodError Accessing undefined subclass methods Use TracePoint or defer access
NameError Referencing undefined constants Check with const_defined?
SystemStackError Infinite recursion Avoid creating subclasses in callbacks
ThreadError Concurrent modification Use thread-safe data structures

Integration Points

Framework Usage Pattern Implementation
Rails ActiveRecord Model registration Automatic table name inference
Sinatra Route inheritance Path prefix copying
RSpec Test case setup Shared example copying
Dry-rb Contract inheritance Validation rule merging