Overview
Ruby constructors control object creation and initialization through the initialize
method. When new
is called on a class, Ruby allocates memory for the object, then automatically invokes initialize
if defined. The initialize
method cannot be called directly on instances and serves as the primary mechanism for setting initial object state.
The constructor system centers around method definition patterns, parameter handling, and inheritance behavior. Ruby passes arguments from new
directly to initialize
, enabling flexible object creation patterns. The method returns the newly created object regardless of what initialize
itself returns.
class Book
def initialize(title, author)
@title = title
@author = author
end
end
book = Book.new("1984", "Orwell")
# => #<Book:0x00007fb8b1234567 @title="1984", @author="Orwell">
Ruby supports constructor overloading through optional parameters, keyword arguments, and variable argument lists. Default values, splat operators, and keyword argument patterns provide multiple approaches to flexible object initialization.
class Database
def initialize(host = 'localhost', port: 5432, **options)
@host = host
@port = port
@options = options
end
end
db1 = Database.new
db2 = Database.new('remote.example.com', port: 3306, ssl: true)
Constructor inheritance follows Ruby's method inheritance rules. Subclasses inherit parent constructors unless they define their own initialize
method. The super
keyword enables calling parent constructors from child classes, maintaining initialization chains across inheritance hierarchies.
Basic Usage
Constructor definition follows standard Ruby method syntax with the reserved name initialize
. The method accepts any combination of positional arguments, keyword arguments, and blocks. Instance variables typically get assigned within the constructor to establish object state.
class User
def initialize(name, email, age = nil)
@name = name
@email = email
@age = age
@created_at = Time.now
end
attr_reader :name, :email, :age, :created_at
end
user = User.new("Alice", "alice@example.com", 30)
user.name # => "Alice"
Keyword arguments provide named parameter access, improving readability for constructors with many parameters. Ruby supports both required and optional keyword arguments, with default values specified using standard assignment syntax.
class ServerConfig
def initialize(host:, port: 80, ssl: false, timeout: 30)
@host = host
@port = port
@ssl = ssl
@timeout = timeout
end
def url
protocol = @ssl ? 'https' : 'http'
"#{protocol}://#{@host}:#{@port}"
end
end
config = ServerConfig.new(host: 'api.example.com', ssl: true, port: 443)
config.url # => "https://api.example.com:443"
Variable argument lists handle dynamic parameter counts through splat operators. The *args
pattern collects positional arguments into an array, while **kwargs
collects keyword arguments into a hash. These patterns enable flexible constructor interfaces.
class Logger
def initialize(*tags, level: :info, **metadata)
@tags = tags
@level = level
@metadata = metadata
end
def log(message)
tag_str = @tags.empty? ? "" : "[#{@tags.join(', ')}] "
puts "#{@level.upcase}: #{tag_str}#{message}"
puts "Metadata: #{@metadata}" unless @metadata.empty?
end
end
logger = Logger.new('auth', 'api', level: :debug, service: 'web', version: '2.1')
logger.log("User authenticated")
# => DEBUG: [auth, api] User authenticated
# => Metadata: {:service=>"web", :version=>"2.1"}
Block parameters in constructors enable initialization-time configuration patterns. The constructor can yield to provided blocks, allowing callers to configure objects during creation through block execution.
class ConnectionPool
def initialize(size = 5, &block)
@size = size
@connections = []
@factory = block || -> { StandardConnection.new }
end
def acquire
@connections.empty? ? @factory.call : @connections.pop
end
end
pool = ConnectionPool.new(10) do
CustomConnection.new(timeout: 5000, retries: 3)
end
Advanced Usage
Constructor patterns become complex when handling inheritance hierarchies, factory methods, and dynamic object creation. The super
keyword controls parent constructor calls, with different behaviors based on argument passing patterns.
class Animal
def initialize(name, species)
@name = name
@species = species
@energy = 100
end
end
class Dog < Animal
def initialize(name, breed, owner = nil)
super(name, 'Canine') # Explicit arguments
@breed = breed
@owner = owner
@tricks = []
end
def learn_trick(trick)
@tricks << trick
@energy -= 10
end
end
class Cat < Animal
def initialize(name, indoor: true, **traits)
super(name, 'Feline')
@indoor = indoor
@traits = traits
@territory = indoor ? :house : :neighborhood
end
end
dog = Dog.new("Rex", "German Shepherd", "Alice")
cat = Cat.new("Whiskers", indoor: false, color: "tabby", temperament: "aloof")
Metaprogramming in constructors enables dynamic attribute assignment and configuration-driven object creation. The instance_variable_set
method assigns instance variables from hash keys, while define_method
creates accessors dynamically.
class ConfigurableModel
def initialize(attributes = {}, **options)
@strict_mode = options.fetch(:strict, false)
attributes.each do |key, value|
instance_var = "@#{key}".to_sym
if @strict_mode && !self.class.valid_attributes.include?(key.to_sym)
raise ArgumentError, "Invalid attribute: #{key}"
end
instance_variable_set(instance_var, value)
end
create_accessors(attributes.keys) if options[:create_accessors]
validate_required_attributes if options[:validate]
end
def self.valid_attributes(*attrs)
@valid_attributes = attrs if attrs.any?
@valid_attributes ||= []
end
private
def create_accessors(keys)
keys.each do |key|
self.class.define_method(key) { instance_variable_get("@#{key}") }
self.class.define_method("#{key}=") { |val| instance_variable_set("@#{key}", val) }
end
end
def validate_required_attributes
required = self.class.instance_variable_get(:@required_attributes) || []
required.each do |attr|
unless instance_variable_defined?("@#{attr}")
raise ArgumentError, "Required attribute missing: #{attr}"
end
end
end
end
class User < ConfigurableModel
valid_attributes :name, :email, :role, :active
@required_attributes = [:name, :email]
end
user = User.new(
{ name: "John", email: "john@example.com", role: "admin" },
strict: true,
create_accessors: true,
validate: true
)
Factory patterns within constructors handle complex object creation logic, conditional instantiation, and object composition. Class methods can serve as alternative constructors, providing semantic interfaces for different creation contexts.
class DatabaseConnection
attr_reader :adapter, :host, :database, :pool_size
def initialize(adapter:, host:, database:, pool_size: 5, **connection_opts)
@adapter = adapter
@host = host
@database = database
@pool_size = pool_size
@connection_opts = connection_opts
@connected = false
establish_connection
end
def self.from_url(url, **options)
uri = URI.parse(url)
adapter = uri.scheme
host = uri.host
database = uri.path[1..-1]
connection_opts = {}
if uri.userinfo
connection_opts[:username], connection_opts[:password] = uri.userinfo.split(':')
end
connection_opts[:port] = uri.port if uri.port
new(
adapter: adapter,
host: host,
database: database,
**connection_opts.merge(options)
)
end
def self.from_config(config_hash)
required_keys = [:adapter, :host, :database]
missing_keys = required_keys - config_hash.keys
raise ArgumentError, "Missing configuration keys: #{missing_keys}" if missing_keys.any?
new(**config_hash)
end
private
def establish_connection
case @adapter
when 'postgresql', 'postgres'
connect_postgresql
when 'mysql', 'mysql2'
connect_mysql
when 'sqlite', 'sqlite3'
connect_sqlite
else
raise ArgumentError, "Unsupported adapter: #{@adapter}"
end
@connected = true
end
end
# Usage patterns
db1 = DatabaseConnection.from_url("postgresql://user:pass@localhost/myapp")
db2 = DatabaseConnection.from_config(
adapter: 'mysql2',
host: 'db.example.com',
database: 'production',
pool_size: 20
)
Constructor chaining patterns handle complex initialization sequences where objects depend on other objects or require multi-step setup processes. Builder patterns integrate with constructors to provide fluent interfaces for object configuration.
class HTTPClient
def initialize(base_url, **options)
@base_url = base_url
@timeout = options[:timeout] || 30
@retry_count = options[:retries] || 3
@headers = options[:headers] || {}
@middleware = []
@interceptors = []
configure_defaults
yield(self) if block_given?
finalize_setup
end
def add_middleware(middleware)
@middleware << middleware
self
end
def add_interceptor(&block)
@interceptors << block
self
end
def with_auth(token)
@headers['Authorization'] = "Bearer #{token}"
self
end
private
def configure_defaults
@headers['User-Agent'] ||= 'Ruby HTTP Client 1.0'
@headers['Accept'] ||= 'application/json'
end
def finalize_setup
validate_configuration
setup_connection_pool
register_cleanup_handlers
end
def validate_configuration
raise ArgumentError, "Invalid timeout: #{@timeout}" if @timeout <= 0
raise ArgumentError, "Invalid retry count: #{@retry_count}" if @retry_count < 0
end
end
client = HTTPClient.new('https://api.example.com', timeout: 60, retries: 5) do |c|
c.add_middleware(LoggingMiddleware.new)
.add_middleware(RetryMiddleware.new)
.with_auth('abc123')
.add_interceptor { |req| req.headers['X-Request-ID'] = SecureRandom.uuid }
end
Error Handling & Debugging
Constructor error handling centers around argument validation, dependency checking, and resource allocation failures. Ruby raises ArgumentError
for parameter mismatches, but custom validation requires explicit error handling within initialize
methods.
class BankAccount
attr_reader :account_number, :balance, :account_type
def initialize(account_number, initial_balance = 0, account_type: :checking)
validate_account_number(account_number)
validate_initial_balance(initial_balance)
validate_account_type(account_type)
@account_number = account_number
@balance = initial_balance.to_f
@account_type = account_type
@transactions = []
@created_at = Time.now
register_account
rescue StandardError => e
cleanup_partial_initialization
raise e
end
private
def validate_account_number(number)
raise ArgumentError, "Account number cannot be nil" if number.nil?
raise ArgumentError, "Account number must be a string" unless number.is_a?(String)
raise ArgumentError, "Invalid account number format" unless number.match?(/\A\d{10,12}\z/)
raise ArgumentError, "Account number already exists" if self.class.exists?(number)
end
def validate_initial_balance(balance)
raise ArgumentError, "Initial balance cannot be nil" if balance.nil?
raise TypeError, "Initial balance must be numeric" unless balance.respond_to?(:to_f)
raise ArgumentError, "Initial balance cannot be negative" if balance.to_f < 0
raise ArgumentError, "Initial balance exceeds maximum" if balance.to_f > 1_000_000
end
def validate_account_type(type)
valid_types = [:checking, :savings, :business, :investment]
raise ArgumentError, "Invalid account type: #{type}" unless valid_types.include?(type)
end
def register_account
AccountRegistry.register(self)
rescue RegistrationError => e
raise ArgumentError, "Failed to register account: #{e.message}"
end
def cleanup_partial_initialization
AccountRegistry.unregister(@account_number) if @account_number
@transactions&.clear
end
def self.exists?(account_number)
AccountRegistry.exists?(account_number)
end
end
# Error scenarios
begin
BankAccount.new("123", -100)
rescue ArgumentError => e
puts "Validation failed: #{e.message}"
end
begin
BankAccount.new("12345", 1000, account_type: :invalid)
rescue ArgumentError => e
puts "Invalid configuration: #{e.message}"
end
Resource management in constructors requires careful handling of external resources, network connections, and file handles. Ruby's automatic garbage collection doesn't guarantee resource cleanup timing, making explicit resource management critical for constructors that allocate external resources.
class FileProcessor
def initialize(input_path, output_path, **options)
@input_path = validate_path(input_path, :read)
@output_path = validate_path(output_path, :write)
@chunk_size = options[:chunk_size] || 8192
@temp_files = []
@resources = []
begin
setup_processing_environment
allocate_buffers
create_temp_directory
open_file_handles
rescue => error
cleanup_resources
raise ProcessingError, "Failed to initialize processor: #{error.message}"
end
end
def finalize
cleanup_resources
end
private
def validate_path(path, mode)
raise ArgumentError, "Path cannot be nil" if path.nil?
raise ArgumentError, "Path must be a string" unless path.is_a?(String)
case mode
when :read
raise ArgumentError, "Input file does not exist: #{path}" unless File.exist?(path)
raise ArgumentError, "Input file is not readable: #{path}" unless File.readable?(path)
when :write
dir = File.dirname(path)
raise ArgumentError, "Output directory does not exist: #{dir}" unless Dir.exist?(dir)
raise ArgumentError, "Output directory is not writable: #{dir}" unless File.writable?(dir)
end
File.expand_path(path)
end
def setup_processing_environment
@temp_dir = Dir.mktmpdir('file_processor')
@resources << @temp_dir
end
def allocate_buffers
@input_buffer = ' ' * @chunk_size
@output_buffer = ' ' * @chunk_size
rescue NoMemoryError
raise ProcessingError, "Insufficient memory for buffers"
end
def create_temp_directory
@work_dir = File.join(@temp_dir, 'work')
Dir.mkdir(@work_dir)
rescue SystemCallError => e
raise ProcessingError, "Cannot create work directory: #{e.message}"
end
def open_file_handles
@input_file = File.open(@input_path, 'rb')
@resources << @input_file
@output_file = File.open(@output_path, 'wb')
@resources << @output_file
rescue SystemCallError => e
raise ProcessingError, "Cannot open files: #{e.message}"
end
def cleanup_resources
@resources.reverse_each do |resource|
case resource
when IO
resource.close unless resource.closed?
when String
FileUtils.rm_rf(resource) if Dir.exist?(resource)
end
rescue => e
warn "Warning: Failed to cleanup resource #{resource}: #{e.message}"
end
@resources.clear
end
class ProcessingError < StandardError; end
end
Debugging constructor issues requires systematic approaches to identify initialization failures, parameter problems, and state inconsistencies. Logging, introspection, and debugging hooks provide visibility into constructor execution.
class DebuggableService
DEBUG_MODE = ENV['DEBUG_CONSTRUCTORS'] == 'true'
def initialize(config, **options)
@debug_info = { start_time: Time.now, steps: [] } if DEBUG_MODE
debug_step("Starting initialization")
debug_step("Config: #{config.inspect}")
debug_step("Options: #{options.inspect}")
begin
@config = normalize_config(config)
debug_step("Config normalized: #{@config.inspect}")
@dependencies = resolve_dependencies(options[:dependencies] || [])
debug_step("Dependencies resolved: #{@dependencies.keys}")
@state = initialize_state(options[:initial_state])
debug_step("State initialized: #{@state.inspect}")
validate_configuration
debug_step("Configuration validated")
rescue => error
debug_step("Error occurred: #{error.class} - #{error.message}")
debug_step("Backtrace: #{error.backtrace.first(5).join(', ')}")
dump_debug_info if DEBUG_MODE
raise error
end
debug_step("Initialization completed successfully")
dump_debug_info if DEBUG_MODE
end
def debug_info
@debug_info
end
private
def debug_step(message)
return unless DEBUG_MODE
@debug_info[:steps] << {
step: @debug_info[:steps].length + 1,
timestamp: Time.now,
message: message,
memory_usage: get_memory_usage,
instance_variables: instance_variables.map { |var|
[var, instance_variable_defined?(var)]
}.to_h
}
end
def dump_debug_info
total_time = Time.now - @debug_info[:start_time]
puts "\n=== Constructor Debug Information ==="
puts "Total initialization time: #{total_time.round(4)}s"
puts "Steps executed: #{@debug_info[:steps].length}"
puts
@debug_info[:steps].each do |step|
elapsed = step[:timestamp] - @debug_info[:start_time]
puts "Step #{step[:step]} (#{elapsed.round(4)}s): #{step[:message]}"
puts " Memory: #{step[:memory_usage]}KB" if step[:memory_usage]
puts " Instance vars: #{step[:instance_variables].keys.join(', ')}"
end
puts "=== End Debug Information ===\n"
end
def get_memory_usage
return nil unless respond_to?(:memory_usage, true)
GC.start
ObjectSpace.memsize_of(self) / 1024
rescue
nil
end
def normalize_config(config)
case config
when String
{ url: config }
when Hash
config.dup
else
raise ArgumentError, "Config must be String or Hash, got #{config.class}"
end
end
def resolve_dependencies(dep_list)
dep_list.each_with_object({}) do |dep_name, resolved|
resolved[dep_name] = DependencyContainer.resolve(dep_name)
end
rescue DependencyError => e
raise ArgumentError, "Dependency resolution failed: #{e.message}"
end
def initialize_state(initial_state)
case initial_state
when nil
{}
when Hash
initial_state.dup
else
raise ArgumentError, "Initial state must be Hash or nil"
end
end
def validate_configuration
required_keys = [:url, :timeout]
missing_keys = required_keys - @config.keys
raise ArgumentError, "Missing required config: #{missing_keys}" if missing_keys.any?
end
end
# Debug usage
service = DebuggableService.new(
{ url: 'http://example.com', timeout: 30 },
dependencies: [:logger, :cache],
initial_state: { retries: 3 }
)
Common Pitfalls
Constructor parameter handling creates subtle issues around argument passing, default value evaluation, and mutable default objects. Ruby evaluates default values at method definition time, not invocation time, causing shared mutable defaults across instances.
# WRONG - Shared mutable default
class ShoppingCart
def initialize(items = [])
@items = items
end
def add_item(item)
@items << item
end
end
cart1 = ShoppingCart.new
cart2 = ShoppingCart.new
cart1.add_item("apple")
cart2.instance_variable_get(:@items) # => ["apple"] - UNEXPECTED!
# CORRECT - New default per instance
class ShoppingCart
def initialize(items = nil)
@items = items || []
end
def add_item(item)
@items << item
end
end
# BETTER - Explicit nil default
class ShoppingCart
def initialize(items: nil)
@items = items&.dup || []
end
end
Inheritance and super calls produce unexpected behavior when parameter lists change between parent and child constructors. Implicit super calls pass all arguments, while explicit super calls control argument passing precisely.
class Vehicle
def initialize(make, model, year)
@make = make
@model = model
@year = year
@fuel_level = 100
end
end
# WRONG - Implicit super passes all arguments
class Car < Vehicle
def initialize(make, model, year, doors)
@doors = doors
super # Passes make, model, year, doors to parent - ERROR!
end
end
# CORRECT - Explicit argument control
class Car < Vehicle
def initialize(make, model, year, doors)
super(make, model, year) # Only passes required arguments
@doors = doors
end
end
# COMPLEX - Keyword argument handling
class ElectricVehicle < Vehicle
def initialize(make, model, year, battery_capacity:, **options)
super(make, model, year) # Positional args only
@battery_capacity = battery_capacity
@charging_speed = options[:charging_speed] || 'standard'
@range = options[:range] || calculate_range
end
private
def calculate_range
@battery_capacity * 3.5 # Simple calculation
end
end
Instance variable initialization timing creates issues when accessors or other methods get called during constructor execution. Uninitialized instance variables return nil, potentially causing unexpected behavior in dependent code.
class Temperature
attr_reader :celsius, :fahrenheit
def initialize(temp, unit = :celsius)
case unit
when :celsius
@celsius = temp
@fahrenheit = celsius_to_fahrenheit # Uses accessor method
when :fahrenheit
@fahrenheit = temp
@celsius = fahrenheit_to_celsius # Uses accessor method
else
raise ArgumentError, "Invalid unit: #{unit}"
end
validate_temperature # Called after both values set
end
private
def celsius_to_fahrenheit
(@celsius * 9.0 / 5.0) + 32
end
def fahrenheit_to_celsius
(@fahrenheit - 32) * 5.0 / 9.0
end
def validate_temperature
if @celsius < -273.15
raise ArgumentError, "Temperature below absolute zero: #{@celsius}°C"
end
end
end
# PROBLEM - Method ordering matters
class ProblematicTemperature
def initialize(temp, unit = :celsius)
validate_temperature # Called before instance variables set - WRONG!
case unit
when :celsius
@celsius = temp
@fahrenheit = celsius_to_fahrenheit
when :fahrenheit
@fahrenheit = temp
@celsius = fahrenheit_to_celsius
end
end
def validate_temperature
# @celsius might be nil here!
raise ArgumentError, "Invalid temperature" if @celsius.nil? || @celsius < -273.15
end
end
Block handling in constructors creates scoping and variable access issues. Blocks execute in the caller's context, not the object's context, limiting access to instance variables and methods during initialization.
class ConfigLoader
def initialize(config_path, &block)
@config_path = config_path
@config = {}
@processors = []
# WRONG - Block executes in caller context
block.call(self) if block_given?
load_config_file
process_config
end
def add_processor(name, &processor_block)
@processors << { name: name, block: processor_block }
end
def set_default(key, value)
@config[key] ||= value
end
private
def load_config_file
# Load implementation
end
def process_config
@processors.each do |processor|
@config = processor[:block].call(@config)
end
end
end
# Usage reveals the problem
loader = ConfigLoader.new('app.yml') do |config|
# This block runs in caller's context
config.add_processor(:normalize) { |data| normalize_keys(data) } # ERROR if normalize_keys not defined here
config.set_default(:timeout, 30)
end
# BETTER - Provide explicit API
class ConfigLoader
def initialize(config_path)
@config_path = config_path
@config = {}
@processors = []
yield(self) if block_given? # Explicit yielding
load_config_file
process_config
end
def configure(&block)
instance_eval(&block) # Execute in object context
end
end
# Usage with better scoping
loader = ConfigLoader.new('app.yml') do |config_loader|
config_loader.configure do
add_processor(:normalize) { |data| data.transform_keys(&:to_sym) }
set_default(:timeout, 30)
end
end
Constructor exceptions and cleanup present challenges around partial object initialization and resource management. Objects in partially constructed states can leak resources or remain in inconsistent states if error handling doesn't address cleanup properly.
class NetworkService
def initialize(host, port, **options)
@host = host
@port = port
@options = options
@resources = []
@initialized = false
begin
establish_connection
setup_authentication
configure_protocols
register_handlers
@initialized = true
rescue => error
cleanup_resources
raise NetworkServiceError, "Initialization failed: #{error.message}"
end
end
def finalize
cleanup_resources if @initialized
end
private
def establish_connection
@socket = TCPSocket.new(@host, @port)
@resources << @socket
rescue SocketError => e
raise ConnectionError, "Cannot connect to #{@host}:#{@port} - #{e.message}"
end
def setup_authentication
if @options[:auth_token]
authenticate_with_token(@options[:auth_token])
elsif @options[:username] && @options[:password]
authenticate_with_credentials(@options[:username], @options[:password])
end
rescue AuthenticationError => e
raise InitializationError, "Authentication failed: #{e.message}"
end
def authenticate_with_token(token)
response = send_command("AUTH TOKEN #{token}")
raise AuthenticationError, "Invalid token" unless response.success?
end
def cleanup_resources
@resources.reverse_each do |resource|
begin
case resource
when TCPSocket
resource.close unless resource.closed?
when IO
resource.close unless resource.closed?
when Tempfile
resource.unlink
end
rescue => e
warn "Failed to cleanup resource: #{e.message}"
end
end
@resources.clear
end
class NetworkServiceError < StandardError; end
class ConnectionError < NetworkServiceError; end
class AuthenticationError < NetworkServiceError; end
class InitializationError < NetworkServiceError; end
end
# Proper error handling usage
begin
service = NetworkService.new('api.example.com', 443, auth_token: 'invalid')
rescue NetworkService::NetworkServiceError => e
puts "Service initialization failed: #{e.message}"
# Object was never fully created, no cleanup needed
end
Reference
Constructor Method Patterns
Pattern | Syntax | Description |
---|---|---|
initialize |
def initialize(params) |
Standard constructor method |
Optional Parameters | def initialize(required, optional = default) |
Parameters with default values |
Keyword Arguments | def initialize(required:, optional: default) |
Named parameter access |
Variable Arguments | def initialize(*args, **kwargs) |
Dynamic argument collection |
Block Parameter | def initialize(&block) |
Block acceptance in constructor |
Parameter Types and Handling
Type | Example | Behavior |
---|---|---|
Required Positional | def initialize(name, email) |
Must be provided in order |
Optional Positional | def initialize(name, age = 0) |
Default used if not provided |
Required Keyword | def initialize(name:, email:) |
Must be provided by name |
Optional Keyword | def initialize(timeout: 30) |
Default used if not provided |
Splat Arguments | def initialize(*items) |
Collects positional args into array |
Double Splat | def initialize(**options) |
Collects keyword args into hash |
Inheritance and Super Patterns
Super Usage | Behavior | Example |
---|---|---|
super |
Passes all current arguments | def initialize(a, b); super; end |
super() |
Passes no arguments | def initialize(a, b); super(); end |
super(args) |
Passes specified arguments | def initialize(a, b); super(a); end |
No super call | Parent constructor not called | def initialize(a, b); @a = a; end |
Common Constructor Validations
# Parameter presence validation
def initialize(name, email)
raise ArgumentError, "Name cannot be nil" if name.nil?
raise ArgumentError, "Name cannot be empty" if name.to_s.strip.empty?
@name = name
@email = email
end
# Type validation
def initialize(count)
raise TypeError, "Count must be numeric" unless count.respond_to?(:to_i)
raise ArgumentError, "Count must be positive" if count.to_i <= 0
@count = count.to_i
end
# Range validation
def initialize(percentage)
unless (0..100).cover?(percentage)
raise ArgumentError, "Percentage must be between 0 and 100"
end
@percentage = percentage
end
# Format validation
def initialize(email)
unless email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
raise ArgumentError, "Invalid email format"
end
@email = email
end
Error Classes and Handling
Error Class | Usage | Example |
---|---|---|
ArgumentError |
Invalid arguments | raise ArgumentError, "Value out of range" |
TypeError |
Wrong argument types | raise TypeError, "Expected String, got #{val.class}" |
StandardError |
General errors | Custom subclasses for specific failures |
NoMethodError |
Missing methods | Typically raised by Ruby automatically |
Constructor Debugging Techniques
# Debug logging
def initialize(config)
puts "Initializing with config: #{config.inspect}" if ENV['DEBUG']
@config = config
puts "Instance variables: #{instance_variables}" if ENV['DEBUG']
end
# Parameter inspection
def initialize(**params)
required = [:host, :port]
provided = params.keys
missing = required - provided
extra = provided - required - [:timeout, :retries] # known optional
puts "Missing required: #{missing}" if missing.any?
puts "Unknown parameters: #{extra}" if extra.any?
# Continue with initialization
end
# State validation
def initialize(data)
@data = data
validate_state
end
private
def validate_state
errors = []
errors << "Data is nil" if @data.nil?
errors << "Data is empty" if @data.respond_to?(:empty?) && @data.empty?
raise ArgumentError, "Invalid state: #{errors.join(', ')}" if errors.any?
end
Memory and Performance Considerations
Concern | Impact | Mitigation |
---|---|---|
Large default objects | Shared mutable state | Use nil defaults, create in constructor |
Complex validations | Slow initialization | Cache validation results, lazy validation |
Resource allocation | Memory leaks | Implement cleanup, use finalizers |
Deep inheritance | Method resolution overhead | Limit inheritance depth, prefer composition |
Block execution | Context switching | Minimize block usage in constructors |
Factory Method Patterns
class DatabaseConnection
# Standard constructor
def initialize(adapter:, host:, database:, **options)
# Implementation
end
# URL-based factory
def self.from_url(url)
# Parse URL and call new
end
# Configuration-based factory
def self.from_config(config_hash)
# Validate and call new
end
# Environment-based factory
def self.from_env(prefix = 'DB')
# Read ENV variables and call new
end
# Connection string factory
def self.parse(connection_string)
# Parse string format and call new
end
end