CrackedRuby logo

CrackedRuby

Class and Module Queries

Ruby Class and Module Queries documentation covering reflection methods for inspecting class hierarchies, module inclusion, method definitions, and runtime type checking.

Metaprogramming Reflection and Introspection
5.1.2

Overview

Class and Module Queries provide Ruby's reflection and introspection capabilities, allowing programs to examine class hierarchies, module relationships, and method definitions at runtime. Ruby implements these queries through methods available on Object, Class, Module, and Kernel that return information about an object's type, its class structure, and available methods.

The core query methods operate on Ruby's object model where every value is an object, every object has a class, and classes themselves are objects. Ruby maintains detailed metadata about class inheritance chains, included modules, and method definitions that these query methods expose programmatically.

class Animal
  def speak; end
end

class Dog < Animal
  include Comparable
  
  def bark; end
end

dog = Dog.new
dog.class                    # => Dog
dog.class.superclass         # => Animal
dog.class.included_modules   # => [Comparable, Kernel]

Class queries examine inheritance relationships and class metadata, while module queries focus on module inclusion and composition. Method queries determine what methods an object responds to and how those methods are defined. Type checking queries verify object relationships and compatibility.

dog.is_a?(Animal)           # => true
dog.respond_to?(:bark)      # => true
Dog.method_defined?(:speak) # => true

Ruby's query system handles the complexity of eigenclasses, module prepending, and method visibility while providing consistent interfaces for runtime inspection. These queries form the foundation for metaprogramming, testing frameworks, serialization libraries, and debugging tools.

Basic Usage

Class Hierarchy Queries

The class method returns an object's immediate class, while superclass traverses up the inheritance chain. Ruby classes form a hierarchy where every class except BasicObject has a parent class.

class Vehicle; end
class Car < Vehicle; end
class Sedan < Car; end

sedan = Sedan.new
sedan.class              # => Sedan
sedan.class.superclass   # => Car
Car.superclass           # => Vehicle
Vehicle.superclass       # => Object
Object.superclass        # => BasicObject
BasicObject.superclass   # => nil

The ancestors method returns the complete inheritance chain including included modules, showing the method lookup path Ruby follows when resolving method calls.

module Trackable
  def track; end
end

class Vehicle
  include Trackable
end

class Car < Vehicle; end

Car.ancestors
# => [Car, Vehicle, Trackable, Object, Kernel, BasicObject]

Module Inclusion Queries

Ruby distinguishes between modules included directly in a class versus those inherited from parent classes. The included_modules method returns modules mixed into a specific class.

module Fuel
  def refuel; end
end

module Electric
  def charge; end
end

class HybridCar < Car
  include Fuel
  include Electric
end

HybridCar.included_modules
# => [Electric, Fuel, Kernel]

Car.included_modules
# => [Kernel]

The include? method checks whether a class or module appears in the ancestors chain, while < and > compare class hierarchy relationships.

HybridCar < Car          # => true
Car < Vehicle            # => true
HybridCar.include?(Fuel) # => true
Car.include?(Fuel)       # => false

Type Checking Queries

Type checking methods determine object compatibility and class relationships. The is_a? and kind_of? methods check whether an object is an instance of a class or its ancestors.

hybrid = HybridCar.new
hybrid.is_a?(HybridCar)  # => true
hybrid.is_a?(Car)        # => true
hybrid.is_a?(Vehicle)    # => true
hybrid.is_a?(String)     # => false

The instance_of? method performs strict type checking, returning true only for the exact class match without considering inheritance.

hybrid.instance_of?(HybridCar) # => true
hybrid.instance_of?(Car)       # => false
hybrid.instance_of?(Vehicle)   # => false

Method Definition Queries

Method queries determine what methods an object can respond to and how those methods are defined. The respond_to? method checks method availability, including inherited and mixed-in methods.

hybrid.respond_to?(:refuel)      # => true
hybrid.respond_to?(:charge)      # => true
hybrid.respond_to?(:track)       # => true
hybrid.respond_to?(:nonexistent) # => false

Class-level method queries examine method definitions within classes and modules using method_defined?, private_method_defined?, and protected_method_defined?.

class SecureCar < Car
  private
  
  def start_engine
    authenticate
  end
  
  def authenticate; end
end

SecureCar.method_defined?(:start_engine)         # => false
SecureCar.private_method_defined?(:start_engine) # => true
SecureCar.method_defined?(:track)                # => true

Advanced Usage

Eigenclass and Singleton Method Queries

Ruby creates eigenclasses (singleton classes) for individual objects to hold their unique methods. Eigenclass queries reveal these hidden classes and their method definitions.

class Robot; end

robot = Robot.new

# Define singleton method
def robot.self_destruct
  puts "Boom!"
end

# Access eigenclass
eigenclass = robot.singleton_class
eigenclass.instance_methods(false) # => [:self_destruct]
eigenclass.superclass              # => Robot

# Check for singleton methods
robot.singleton_methods            # => [:self_destruct]
Robot.singleton_methods            # => []

Class methods exist as instance methods on the class's eigenclass. Understanding this relationship helps when querying class-level method definitions.

class DatabaseConnection
  def self.establish
    puts "Connecting..."
  end
  
  class << self
    def close_all
      puts "Closing all connections"
    end
    
    private
    
    def validate_config
      # validation logic
    end
  end
end

DatabaseConnection.singleton_class.instance_methods(false)
# => [:close_all, :establish]

DatabaseConnection.singleton_class.private_instance_methods(false)
# => [:validate_config]

Constant Resolution Queries

Ruby's constant lookup follows specific scoping rules. Constant queries examine how constants are resolved within class and module hierarchies.

module Config
  DATABASE_URL = "localhost:5432"
  
  class Settings
    API_KEY = "secret123"
    
    def self.database_defined?
      const_defined?(:DATABASE_URL)          # => false (local scope)
    end
    
    def self.database_defined_inherited?
      const_defined?(:DATABASE_URL, true)    # => true (inherited scope)
    end
  end
end

Config.const_defined?(:DATABASE_URL)                    # => true
Config::Settings.const_defined?(:API_KEY)               # => true
Config.const_get(:DATABASE_URL)                         # => "localhost:5432"
Config::Settings.constants                              # => [:API_KEY]
Config.constants                                        # => [:DATABASE_URL, :Settings]

Method Source Location Queries

Ruby tracks where methods are defined, including file paths and line numbers. These queries help with debugging and code analysis tools.

class Analytics
  def track_event(name)
    # implementation
  end
  
  define_method(:track_user) do |user|
    # dynamic method definition
  end
end

analytics = Analytics.new

# Get method objects
track_event_method = analytics.method(:track_event)
track_user_method = analytics.method(:track_user)

# Query source locations
track_event_method.source_location
# => ["/path/to/file.rb", 42]

track_user_method.source_location
# => ["/path/to/file.rb", 46]

# Query method ownership
Analytics.instance_method(:track_event).owner # => Analytics

Module Prepending Queries

Module prepending alters method lookup order by placing modules before the including class in the ancestors chain. Prepend queries reveal this modified hierarchy.

module Logging
  def save
    puts "Logging save operation"
    super
  end
end

class Document
  def save
    puts "Saving document"
  end
end

class AuditedDocument < Document
  prepend Logging
end

AuditedDocument.ancestors
# => [Logging, AuditedDocument, Document, Object, Kernel, BasicObject]

# Method resolution follows prepended module first
audited = AuditedDocument.new
audited.save
# Logging save operation
# Saving document

Dynamic Method Query Patterns

Complex applications often need to query method definitions dynamically, filtering by visibility, source, or naming patterns.

class ApiController
  def index; end
  def show; end
  def create; end
  
  private
  def authenticate; end
  def authorize; end
  
  protected
  def current_user; end
end

# Get public action methods
public_actions = ApiController.public_instance_methods(false)
                              .select { |m| %w[index show create update destroy].include?(m.to_s) }
# => [:index, :show, :create]

# Get private helper methods
private_helpers = ApiController.private_instance_methods(false)
# => [:authenticate, :authorize]

# Find methods matching pattern
auth_methods = (ApiController.private_instance_methods(false) +
                ApiController.protected_instance_methods(false))
               .select { |m| m.to_s.start_with?('auth') }
# => [:authenticate, :authorize]

Common Pitfalls

Class vs Instance Method Confusion

Ruby distinguishes between class methods and instance methods, but query methods can return confusing results when the distinction isn't clear. Methods defined on classes exist as instance methods of their eigenclass.

class User
  def self.find(id)  # class method
    new
  end
  
  def save           # instance method
    puts "Saving user"
  end
end

# Common mistake: expecting instance methods on the class
User.method_defined?(:find)    # => false (find is not an instance method)
User.method_defined?(:save)    # => true

# Correct approach for class methods
User.singleton_class.method_defined?(:find)     # => true
User.respond_to?(:find)                         # => true

# Instance method queries work on instances
user = User.new
user.respond_to?(:save)        # => true
user.respond_to?(:find)        # => false

Eigenclass Inheritance Gotchas

Eigenclasses have their own inheritance relationships that don't always match expectations. Singleton methods don't inherit the same way instance methods do.

class Animal
  def self.species_count
    100
  end
end

class Dog < Animal
end

# Class methods are inherited
Dog.species_count              # => 100
Dog.respond_to?(:species_count) # => true

# But eigenclass relationships differ
Animal.singleton_class.superclass # => #<Class:Object>
Dog.singleton_class.superclass    # => #<Class:Animal>

# Singleton methods don't appear in instance method queries
Dog.method_defined?(:species_count) # => false

# Individual object eigenclasses behave differently
dog1 = Dog.new
dog2 = Dog.new

def dog1.bark_loud
  puts "WOOF!"
end

dog1.respond_to?(:bark_loud)      # => true
dog2.respond_to?(:bark_loud)      # => false
dog1.singleton_methods            # => [:bark_loud]
dog2.singleton_methods            # => []

Module Inclusion Order Effects

The order of module inclusion affects method lookup and the results of ancestor queries. Later included modules appear earlier in the ancestors chain.

module A
  def test; "A"; end
end

module B  
  def test; "B"; end
end

class Example
  include A
  include B
end

# B overrides A because it was included last
Example.ancestors             # => [Example, B, A, Object, Kernel, BasicObject]
Example.new.test              # => "B"

# Prepending reverses this expectation
class PrependExample  
  include A
  prepend B
end

PrependExample.ancestors      # => [B, PrependExample, A, Object, Kernel, BasicObject]
PrependExample.new.test       # => "B"

Constant Resolution Scope Issues

Constant lookup follows lexical scope first, then inheritance hierarchy. This creates unexpected behavior when constants with the same name exist at different scoping levels.

DATABASE_URL = "global"

module Config
  DATABASE_URL = "config"
  
  class Settings
    def self.get_url
      DATABASE_URL  # Returns "config", not "global"
    end
    
    def self.get_global_url
      ::DATABASE_URL  # Explicitly reference global constant
    end
  end
  
  module Nested
    def self.get_url
      DATABASE_URL  # Still returns "config" from Config module
    end
  end
end

Config::Settings.get_url        # => "config"
Config::Settings.get_global_url # => "global"
Config::Nested.get_url          # => "config"

# const_defined? behavior varies with inheritance flag
Config::Settings.const_defined?(:DATABASE_URL)        # => false
Config::Settings.const_defined?(:DATABASE_URL, true)  # => true

Method Visibility and respond_to? Behavior

The respond_to? method respects method visibility by default, but this behavior can be overridden. Private and protected method queries require understanding visibility rules.

class SecureSystem
  def public_action
    puts "Public"
  end
  
  protected
  def protected_helper
    puts "Protected"
  end
  
  private
  def private_operation
    puts "Private"
  end
end

system = SecureSystem.new

# Default respond_to? respects visibility
system.respond_to?(:public_action)      # => true
system.respond_to?(:protected_helper)   # => false
system.respond_to?(:private_operation)  # => false

# Override visibility check
system.respond_to?(:protected_helper, true)   # => true
system.respond_to?(:private_operation, true)  # => true

# Class-level method queries ignore visibility context
SecureSystem.method_defined?(:private_operation)     # => true
SecureSystem.private_method_defined?(:private_operation) # => true

# Method calls still respect visibility
begin
  system.private_operation
rescue NoMethodError => e
  puts e.message  # private method `private_operation' called
end

Inherited Method Source Confusion

When methods are inherited or included from modules, their source location points to the original definition, not the including class. This can confuse debugging and introspection tools.

module Timestampable
  def created_at
    @created_at ||= Time.now
  end
end

class User
  include Timestampable
  
  def name
    @name
  end
end

user = User.new

# Method sources point to original definitions
user.method(:name).source_location
# => ["/current/file.rb", 123]

user.method(:created_at).source_location  
# => ["/path/to/timestampable.rb", 45]  # Points to module, not User class

# Owner reflects actual definition location
User.instance_method(:name).owner         # => User
User.instance_method(:created_at).owner   # => Timestampable

Reference

Object Type Query Methods

Method Parameters Returns Description
#class None Class Returns the object's immediate class
#is_a?(class_or_module) Class or Module Boolean Tests if object is instance of class/module or ancestors
#kind_of?(class_or_module) Class or Module Boolean Alias for is_a?
#instance_of?(class) Class Boolean Tests if object is direct instance of class (no inheritance)
#respond_to?(method, include_private=false) Symbol/String, Boolean Boolean Tests if object responds to method call

Class Hierarchy Query Methods

Method Parameters Returns Description
.superclass None Class or nil Returns parent class in inheritance chain
.ancestors None Array<Class, Module> Returns inheritance chain including modules
.included_modules None Array<Module> Returns modules included directly in class
.<(other) Class or Module Boolean or nil Tests if class is subclass of other
.>(other) Class or Module Boolean or nil Tests if class is superclass of other
.include?(module) Module Boolean Tests if module is in ancestors chain

Method Definition Query Methods

Method Parameters Returns Description
.method_defined?(symbol) Symbol or String Boolean Tests if public/protected instance method is defined
.private_method_defined?(symbol) Symbol or String Boolean Tests if private instance method is defined
.protected_method_defined?(symbol) Symbol or String Boolean Tests if protected instance method is defined
.public_method_defined?(symbol) Symbol or String Boolean Tests if public instance method is defined
#singleton_methods(all=true) Boolean Array<Symbol> Returns singleton methods defined on object
.instance_methods(include_super=true) Boolean Array<Symbol> Returns public and protected instance method names
.private_instance_methods(include_super=true) Boolean Array<Symbol> Returns private instance method names
.protected_instance_methods(include_super=true) Boolean Array<Symbol> Returns protected instance method names
.public_instance_methods(include_super=true) Boolean Array<Symbol> Returns public instance method names

Singleton Class Query Methods

Method Parameters Returns Description
#singleton_class None Class Returns object's eigenclass
.singleton_class None Class Returns class's eigenclass
#singleton_methods(all=true) Boolean Array<Symbol> Returns methods defined only on this object

Constant Query Methods

Method Parameters Returns Description
.const_defined?(symbol, inherit=true) Symbol/String, Boolean Boolean Tests if constant is defined in scope
.const_get(symbol, inherit=true) Symbol/String, Boolean Object Returns constant value from scope
.const_set(symbol, value) Symbol/String, Object Object Defines constant in current scope
.constants(inherit=true) Boolean Array<Symbol> Returns constant names defined in scope
.const_missing(symbol) Symbol Object Hook called when constant lookup fails

Method Object Query Methods

Method Parameters Returns Description
#method(symbol) Symbol or String Method Returns Method object for instance method
.instance_method(symbol) Symbol or String UnboundMethod Returns UnboundMethod for class method
Method#owner None Class or Module Returns class/module where method is defined
Method#source_location None Array or nil Returns [filename, line_number] of method definition
Method#parameters None Array Returns parameter information as [type, name] pairs

Query Method Return Types

Boolean Methods: Return true, false, or nil (for comparison operators when objects aren't comparable)

Array Methods: Return empty arrays when no matches found

Class Methods: Return Class objects or nil for BasicObject.superclass

Constant Methods: Raise NameError when constants not found (except const_defined?)

Visibility Levels

Level Description Query Methods
public Callable from anywhere public_method_defined?, public_instance_methods
protected Callable within class hierarchy protected_method_defined?, protected_instance_methods
private Callable only within defining object private_method_defined?, private_instance_methods

Common Query Patterns

# Check if object can handle method before calling
obj.respond_to?(:method_name) && obj.method_name

# Find all methods containing substring
klass.instance_methods.select { |m| m.to_s.include?("save") }

# Get methods defined in class (not inherited)
klass.instance_methods(false)

# Check class relationship
child_class < parent_class

# Verify module inclusion
klass.include?(module_name)

# Get method definition location
obj.method(:method_name).source_location