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 |