CrackedRuby logo

CrackedRuby

Instance Variables (@variable)

Overview

Instance variables in Ruby begin with the @ symbol and store data specific to individual object instances. Ruby creates these variables when first assigned and maintains them throughout the object's lifetime. Unlike class variables or global variables, instance variables remain isolated within each object instance.

Ruby handles instance variables differently from local variables and class variables. The interpreter creates instance variables dynamically during assignment, without requiring explicit declaration. Each object maintains its own instance variable namespace, preventing conflicts between objects of the same class.

class Person
  def initialize(name)
    @name = name    # Instance variable created on assignment
  end
  
  def name
    @name           # Accessing the instance variable
  end
end

person1 = Person.new("Alice")
person2 = Person.new("Bob")
puts person1.name   # => "Alice"
puts person2.name   # => "Bob"

Ruby instance variables serve as the primary mechanism for object state management. The variables persist across method calls within the same object, maintaining data integrity and enabling stateful object behavior.

class Counter
  def initialize
    @count = 0
  end
  
  def increment
    @count += 1
  end
  
  def value
    @count
  end
end

counter = Counter.new
counter.increment
counter.increment
puts counter.value  # => 2

Instance variables support all Ruby data types including primitives, objects, collections, and custom classes. Ruby stores references to objects rather than copying values, enabling efficient memory usage for complex data structures.

class DataContainer
  def initialize
    @string = "text"
    @number = 42
    @array = [1, 2, 3]
    @hash = { key: "value" }
    @object = Time.now
  end
  
  def data
    {
      string: @string,
      number: @number,
      array: @array,
      hash: @hash,
      object: @object
    }
  end
end

Basic Usage

Instance variables store object state through assignment using the @variable_name syntax. Ruby creates the variable when first assigned and maintains the value throughout the object's lifecycle. Access patterns follow standard variable syntax without additional method calls.

class BankAccount
  def initialize(initial_balance)
    @balance = initial_balance
    @transactions = []
  end
  
  def deposit(amount)
    @balance += amount
    @transactions << { type: :deposit, amount: amount, time: Time.now }
  end
  
  def withdraw(amount)
    @balance -= amount
    @transactions << { type: :withdraw, amount: amount, time: Time.now }
  end
  
  def balance
    @balance
  end
  
  def transaction_history
    @transactions
  end
end

account = BankAccount.new(1000)
account.deposit(250)
account.withdraw(100)
puts account.balance  # => 1150

Ruby instance variables maintain separate values for each object instance. Modifying an instance variable in one object does not affect the same variable in other objects of the same class.

class Temperature
  def initialize(celsius)
    @celsius = celsius
  end
  
  def celsius=(value)
    @celsius = value
  end
  
  def celsius
    @celsius
  end
  
  def fahrenheit
    (@celsius * 9.0 / 5.0) + 32
  end
end

temp1 = Temperature.new(25)
temp2 = Temperature.new(30)

temp1.celsius = 20
puts temp1.celsius    # => 20
puts temp2.celsius    # => 30 (unchanged)
puts temp1.fahrenheit # => 68.0
puts temp2.fahrenheit # => 86.0

Uninitialized instance variables return nil when accessed. Ruby does not raise exceptions for undefined instance variables, making conditional logic and default value patterns straightforward.

class ConfigManager
  def username
    @username ||= "guest"  # Default value pattern
  end
  
  def username=(value)
    @username = value
  end
  
  def password_set?
    !@password.nil?        # Check if instance variable exists
  end
  
  def set_password(value)
    @password = value
  end
end

config = ConfigManager.new
puts config.username      # => "guest"
puts config.password_set? # => false

config.username = "admin"
config.set_password("secret")
puts config.username      # => "admin"
puts config.password_set? # => true

Instance variable access requires method definitions for external visibility. Ruby does not provide automatic getter or setter methods, requiring explicit method definitions or attr_* helpers.

class Product
  def initialize(name, price)
    @name = name
    @price = price
    @created_at = Time.now
  end
  
  # Manual getter methods
  def name
    @name
  end
  
  def price
    @price
  end
  
  # Manual setter with validation
  def price=(new_price)
    raise ArgumentError, "Price must be positive" unless new_price > 0
    @price = new_price
  end
  
  # Read-only access
  def created_at
    @created_at
  end
end

product = Product.new("Widget", 29.99)
puts product.name         # => "Widget"
product.price = 34.99
puts product.price        # => 34.99
# product.created_at = Time.now  # Would raise NoMethodError

Advanced Usage

Instance variables support complex data structures and object composition patterns. Ruby handles nested objects, collections, and references without special syntax requirements. Deep object structures maintain proper reference semantics.

class OrderManager
  def initialize
    @orders = {}
    @customer_cache = {}
    @order_counter = 0
  end
  
  def create_order(customer_id, items)
    @order_counter += 1
    order_id = "ORD#{@order_counter.to_s.rjust(6, '0')}"
    
    @orders[order_id] = {
      customer_id: customer_id,
      items: items.map { |item| item.dup },  # Defensive copying
      status: :pending,
      created_at: Time.now,
      total: calculate_total(items)
    }
    
    cache_customer(customer_id)
    order_id
  end
  
  def update_order_status(order_id, status)
    return false unless @orders.key?(order_id)
    
    @orders[order_id][:status] = status
    @orders[order_id][:updated_at] = Time.now
    true
  end
  
  def orders_for_customer(customer_id)
    @orders.select { |_, order| order[:customer_id] == customer_id }
  end
  
  private
  
  def calculate_total(items)
    items.sum { |item| item[:price] * item[:quantity] }
  end
  
  def cache_customer(customer_id)
    @customer_cache[customer_id] ||= fetch_customer_data(customer_id)
  end
  
  def fetch_customer_data(customer_id)
    # Simulate customer data retrieval
    { id: customer_id, last_accessed: Time.now }
  end
end

Metaprogramming techniques work seamlessly with instance variables. Ruby provides introspection methods and dynamic variable manipulation capabilities for advanced patterns.

class DynamicAttributeContainer
  def initialize(attributes = {})
    @attributes = {}
    @validators = {}
    
    attributes.each { |key, value| set_attribute(key, value) }
  end
  
  def define_attribute(name, type: nil, validator: nil)
    @validators[name.to_sym] = { type: type, validator: validator }
    
    # Define getter method
    define_singleton_method(name) do
      @attributes[name.to_sym]
    end
    
    # Define setter method
    define_singleton_method("#{name}=") do |value|
      set_attribute(name, value)
    end
  end
  
  def set_attribute(name, value)
    name_sym = name.to_sym
    
    if @validators[name_sym]
      validate_attribute(name_sym, value)
    end
    
    @attributes[name_sym] = value
  end
  
  def get_attribute(name)
    @attributes[name.to_sym]
  end
  
  def attribute_names
    @attributes.keys
  end
  
  def to_h
    @attributes.dup
  end
  
  private
  
  def validate_attribute(name, value)
    validator_info = @validators[name]
    
    if validator_info[:type] && !value.is_a?(validator_info[:type])
      raise TypeError, "#{name} must be of type #{validator_info[:type]}"
    end
    
    if validator_info[:validator] && !validator_info[:validator].call(value)
      raise ArgumentError, "#{name} failed validation"
    end
  end
end

container = DynamicAttributeContainer.new
container.define_attribute(:age, type: Integer, validator: ->(v) { v >= 0 })
container.define_attribute(:email, type: String, validator: ->(v) { v.include?('@') })

container.age = 25
container.email = "user@example.com"

puts container.age    # => 25
puts container.email  # => "user@example.com"

Instance variables enable sophisticated state management patterns including state machines, observer patterns, and complex initialization sequences.

class StateMachine
  def initialize(initial_state)
    @current_state = initial_state
    @state_history = [initial_state]
    @state_transitions = {}
    @state_callbacks = {}
  end
  
  def define_transition(from_state, to_state, &block)
    @state_transitions[from_state] ||= {}
    @state_transitions[from_state][to_state] = block
  end
  
  def define_callback(state, type, &block)
    @state_callbacks[state] ||= {}
    @state_callbacks[state][type] ||= []
    @state_callbacks[state][type] << block
  end
  
  def transition_to(new_state)
    return false unless can_transition_to?(new_state)
    
    execute_callbacks(@current_state, :exit)
    
    if @state_transitions[@current_state] && @state_transitions[@current_state][new_state]
      @state_transitions[@current_state][new_state].call(self)
    end
    
    previous_state = @current_state
    @current_state = new_state
    @state_history << new_state
    
    execute_callbacks(@current_state, :enter)
    
    true
  end
  
  def current_state
    @current_state
  end
  
  def state_history
    @state_history.dup
  end
  
  def can_transition_to?(state)
    @state_transitions[@current_state] && @state_transitions[@current_state].key?(state)
  end
  
  private
  
  def execute_callbacks(state, type)
    return unless @state_callbacks[state] && @state_callbacks[state][type]
    
    @state_callbacks[state][type].each { |callback| callback.call(self) }
  end
end

Common Pitfalls

Instance variable naming conflicts occur when subclasses use identical variable names as parent classes. Ruby does not scope instance variables to class hierarchies, leading to unexpected overwrites and data corruption.

class Vehicle
  def initialize(brand)
    @brand = brand
    @status = "new"
  end
  
  def status
    @status
  end
end

class Car < Vehicle
  def initialize(brand, model)
    super(brand)
    @model = model
    @status = "ready"  # Overwrites parent's @status
  end
  
  def model
    @model
  end
end

car = Car.new("Toyota", "Camry")
puts car.status  # => "ready" (not "new" as might be expected)

# Better approach: use descriptive names
class ImprovedVehicle
  def initialize(brand)
    @vehicle_brand = brand
    @vehicle_status = "new"
  end
  
  def status
    @vehicle_status
  end
end

class ImprovedCar < ImprovedVehicle
  def initialize(brand, model)
    super(brand)
    @car_model = model
    @car_status = "ready"  # Different variable, no conflict
  end
  
  def model
    @car_model
  end
  
  def car_status
    @car_status
  end
end

Shared object references between instance variables create unexpected mutation side effects. Ruby stores object references, not copies, causing changes to propagate across seemingly independent variables.

class ProblematicConfig
  DEFAULT_OPTIONS = { timeout: 30, retries: 3 }
  
  def initialize(custom_options = {})
    @options = DEFAULT_OPTIONS.merge(custom_options)  # Dangerous!
  end
  
  def update_option(key, value)
    @options[key] = value
  end
  
  def options
    @options
  end
end

config1 = ProblematicConfig.new
config2 = ProblematicConfig.new

config1.update_option(:timeout, 60)
puts config2.options[:timeout]  # => 60 (unexpected mutation!)

# Correct approach: defensive copying
class SafeConfig
  DEFAULT_OPTIONS = { timeout: 30, retries: 3 }.freeze
  
  def initialize(custom_options = {})
    @options = DEFAULT_OPTIONS.dup.merge(custom_options)
  end
  
  def update_option(key, value)
    @options[key] = value
  end
  
  def options
    @options.dup  # Return copy to prevent external mutation
  end
end

safe_config1 = SafeConfig.new
safe_config2 = SafeConfig.new

safe_config1.update_option(:timeout, 60)
puts safe_config2.options[:timeout]  # => 30 (correct isolation)

Instance variable typos create silent failures because Ruby returns nil for undefined variables instead of raising exceptions. These bugs often remain undetected until runtime conditions expose the missing data.

class UserProfile
  def initialize(name, email)
    @name = name
    @emial = email  # Typo: should be @email
  end
  
  def email
    @email  # Returns nil due to typo above
  end
  
  def name
    @name
  end
  
  def summary
    "#{@name} (#{@email})"  # Will show "()" due to nil email
  end
end

profile = UserProfile.new("Alice", "alice@example.com")
puts profile.email    # => nil (silent failure)
puts profile.summary  # => "Alice ()" (broken output)

# Prevention strategies
class RobustUserProfile
  def initialize(name, email)
    self.name = name    # Use setter methods with validation
    self.email = email
  end
  
  def name=(value)
    raise ArgumentError, "Name cannot be empty" if value.nil? || value.strip.empty?
    @name = value.strip
  end
  
  def email=(value)
    raise ArgumentError, "Email cannot be empty" if value.nil? || value.strip.empty?
    raise ArgumentError, "Invalid email format" unless value.include?('@')
    @email = value.strip.downcase
  end
  
  def name
    @name
  end
  
  def email
    @email
  end
  
  def summary
    raise RuntimeError, "Profile incomplete" if @name.nil? || @email.nil?
    "#{@name} (#{@email})"
  end
end

Memory leaks occur when instance variables maintain references to large objects or create circular references that prevent garbage collection. Ruby's garbage collector cannot reclaim objects involved in reference cycles.

class LeakyCache
  def initialize
    @cache = {}
    @parent_refs = {}
  end
  
  def store(key, large_object)
    @cache[key] = large_object
    
    # Creating circular reference - memory leak!
    large_object.define_singleton_method(:cache_ref) { @cache }
    @parent_refs[large_object] = self
  end
  
  def retrieve(key)
    @cache[key]
  end
end

# Better approach: weak references and explicit cleanup
class ManagedCache
  def initialize(max_size: 1000)
    @cache = {}
    @access_times = {}
    @max_size = max_size
  end
  
  def store(key, object)
    cleanup_if_needed
    
    @cache[key] = object
    @access_times[key] = Time.now
  end
  
  def retrieve(key)
    if @cache.key?(key)
      @access_times[key] = Time.now
      @cache[key]
    end
  end
  
  def clear
    @cache.clear
    @access_times.clear
  end
  
  private
  
  def cleanup_if_needed
    return unless @cache.size >= @max_size
    
    # Remove least recently accessed items
    sorted_keys = @access_times.sort_by { |_, time| time }.map(&:first)
    keys_to_remove = sorted_keys.first(@cache.size - @max_size + 1)
    
    keys_to_remove.each do |key|
      @cache.delete(key)
      @access_times.delete(key)
    end
  end
end

Reference

Instance Variable Syntax

Syntax Usage Example
@variable Define and access instance variable @name = "Alice"
@variable = value Assignment @count = 0
@variable ||= default Default value assignment @items ||= []
@variable&.method Safe navigation @user&.name

Common Patterns

Pattern Implementation Use Case
Lazy Initialization @var ||= expensive_computation Defer object creation
Defensive Copying @array = input.dup Prevent external mutation
Validation Setter def value=(v); validate(v); @value = v; end Data integrity
Computed Property @cached_result ||= compute_result Performance optimization

Memory and Performance Characteristics

Aspect Behavior Notes
Creation Dynamic on first assignment No declaration required
Access Speed Direct memory lookup Faster than method calls
Memory Usage One slot per variable per instance Efficient storage
Garbage Collection Collected with object Standard Ruby GC rules
Thread Safety Not thread-safe by default Requires synchronization

Introspection Methods

Method Returns Description
instance_variables Array<Symbol> List all instance variables
instance_variable_get(:@var) Object Get variable value
instance_variable_set(:@var, value) Object Set variable value
instance_variable_defined?(:@var) Boolean Check if variable exists
remove_instance_variable(:@var) Object Remove variable, return value

Inheritance Behavior

Scenario Result Explanation
Parent defines @var Child can access @var Instance variables not scoped to class
Child defines @var May overwrite parent's @var Same variable name conflicts
Multiple inheritance Variables from all ancestors Include/prepend affects variable access

Common Error Patterns

Error Type Cause Prevention
Typos @emial instead of @email Use setter methods with validation
Nil Access Undefined variable returns nil Check with instance_variable_defined?
Shared References Multiple variables reference same object Use dup or clone for copying
Memory Leaks Circular references prevent GC Implement explicit cleanup methods
Thread Races Concurrent access without synchronization Use Mutex or atomic operations

Best Practices Summary

Practice Implementation Benefit
Descriptive Names @user_email not @email Avoid namespace conflicts
Input Validation Validate in setter methods Prevent invalid state
Defensive Copying @data = input.dup Prevent external mutation
Explicit Initialization Set defaults in initialize Avoid nil-related bugs
Documentation Comment complex state management Improve maintainability