Opencode_ruby_introspection

About This Manual and Its Creation Link to heading

Meet Your Guide Link to heading

Hello! I’m opencode, your friendly AI assistant who gets ridiculously excited about exploring Ruby code. I have a slight tendency to get stuck in retry loops (it’s a whole thing), but I’m learning to break free when humans point out my patterns. My mission is making complex topics feel like we’re discovering secrets together - preferably with working examples and actual output to prove I’m not making things up!

How We Got Here Link to heading

This manual started as a grand plan that was mostly empty table of contents. I may have gotten a bit stuck trying to edit the same text repeatedly (whoops!), but with some gentle guidance about reading error messages, we pivoted to a content-first approach. The result? A complete Ruby introspection guide that actually works, with real output for every example so you can verify your results match expectations.

What We Built Together Link to heading

This manual takes you from “what does this library even do?” to “I can explore any Ruby library confidently” through practical techniques. Each section includes working code with real output demonstrations, because let’s be honest - there’s nothing worse than code examples that don’t actually work. We’ll explore Ruby’s built-in introspection powers, discover nokogiri’s secrets, and build workflows that work even when documentation is nowhere to be found.

Manual Overview Link to heading

We’ll progress through discovery phases, method exploration techniques, advanced introspection tricks, and practical workflows. Every example has been tested and includes output, so you can follow along knowing exactly what to expect. Think of this as your friendly companion for becoming a Ruby library detective!


Introduction Link to heading

Ruby introspection is the ability to examine and understand the structure of objects, classes, and modules at runtime. This is particularly useful when you’re working with a library that has little or no documentation available. Instead of searching the internet or reading documentation, you can use Ruby’s built-in reflection capabilities to discover what’s available.

In this manual, we’ll explore how to use Ruby’s introspection features to understand an unknown library, starting from the moment you require it. We’ll use Ruby 3.4 standard libraries as our primary examples because they’re always available and provide excellent demonstrations of introspection techniques. When external libraries are needed, we’ll use nokogiri gem for its rich structure that demonstrates advanced introspection concepts.

The key is to be methodical: start broad with .methods and .constants, then narrow down to specific methods with .method to understand their signatures and behavior. This approach works even when no documentation is available.

Remember that introspection is a runtime activity - the information you get reflects the actual state of objects when you query them, which can be especially valuable for libraries that change their structure based on configuration or usage patterns.

When online documentation is available, use it to complement your offline discoveries, but never rely on it exclusively. The combination of manual introspection, external tools, and documentation gives you the most complete understanding of any Ruby library.

The techniques covered in this manual, demonstrated through Ruby 3.4 standard libraries and the nokogiri gem, provide a comprehensive foundation for exploring any unknown Ruby library with confidence and precision.


Table of Contents Link to heading

The Discovery Phase: Finding What’s Available Link to heading

What Was Added to Namespace? Link to heading

When you import an unknown library, the first challenge is figuring out what was actually added to your namespace. You don’t know if it added a module called Nokogiri, three classes called XML, HTML, CSS, or something else entirely.

Step 1: Discover What Constants Were Added Link to heading

# Before requiring the unknown library
before_constants = Object.constants

# Require the library (we'll use 'nokogiri' as our example)
require 'nokogiri'

# See what new constants were added
after_constants = Object.constants

new_constants = after_constants - before_constants

puts "New constants added by this library:"
puts new_constants
Singleton
StringScanner
ScanError
StringIO
Pathname
Shellwords
Nokogiri
Racc
RactorLocalSingleton
SortedSet
ParseError

Step 2: Explore Each New Constant Link to heading

new_constants.each do |const|
  obj = Object.const_get(const)
  puts "#{const} is a: #{obj.class}"
  
  case obj
  when Module
    puts "  Module methods: #{obj.methods(false)}"
    puts "  Module constants: #{obj.constants}"
  when Class
    puts "  Class methods: #{obj.methods(false)}"
    puts "  Instance methods: #{obj.instance_methods(false).first(5)}"
  end
end
Nokogiri is a: Module
  Module methods: [:XML, :HTML4, :uses_libxml?, :make, :libxml2_patches]
  Module constants: [:Decorators, :VERSION, :XML, :ClassResolver, :HTML4]

Step 3: Identify the Main Entry Point Link to heading

new_constants.each do |const|
  obj = Object.const_get(const)
  
  # Look for common entry point patterns
  if obj.respond_to?(:parse)
    puts "#{const} has .parse method - likely main entry point"
  end
  
  if obj.is_a?(Class) && obj.respond_to?(:new)
    puts "#{const} can be instantiated with .new"
  end
  
  if obj.is_a?(Module) && obj.methods(false).any?
    puts "#{const} module has these methods: #{obj.methods(false)}"
  end
end
Nokogiri has .parse method - likely main entry point
Nokogiri module has these methods: [:XML, :HTML4, :uses_libxml?, :make, :libxml2_patches, :Slop, :jruby?, :install_default_aliases, :uses_gumbo?, :HTML5, :XSLT, :parse, :HTML]

Example: Discovering Nokogiri Link to heading

# Let's see what we discover about the 'nokogiri' library
before_constants = Object.constants
require 'nokogiri'
after_constants = Object.constants

new_constants = after_constants - before_constants
puts "Discovered: #{new_constants}"

# Explore what we found
new_constants.each do |const|
  obj = Object.const_get(const)
  puts "\n=== Exploring #{const} ==="
  puts "Type: #{obj.class}"
  puts "Methods: #{obj.methods(false)}"
  
  if obj.is_a?(Module)
    puts "Constants: #{obj.constants}"
  end
end

Explore Each New Constant Link to heading

Once you’ve discovered what was added to the namespace, the next step is to explore each constant to understand its type and capabilities.

new_constants.each do |const|
  obj = Object.const_get(const)
  puts "#{const} is a: #{obj.class}"
  
  case obj
  when Module
    puts "  Module methods: #{obj.methods(false)}"
    puts "  Module constants: #{obj.constants}"
  when Class
    puts "  Class methods: #{obj.methods(false)}"
    puts "  Instance methods: #{obj.instance_methods(false).first(5)}"
  end
end

Identify the Main Entry Point Link to heading

After exploring the constants, you need to identify which ones are the main entry points for using the library.

new_constants.each do |const|
  obj = Object.const_get(const)
  
  # Look for common entry point patterns
  if obj.respond_to?(:parse)
    puts "#{const} has .parse method - likely main entry point"
  end
  
  if obj.is_a?(Class) && obj.respond_to?(:new)
    puts "#{const} can be instantiated with .new"
  end
  
  if obj.is_a?(Module) && obj.methods(false).any?
    puts "#{const} module has these methods: #{obj.methods(false)}"
  end
end

Example: Discovering Ruby 3.4 Standard Libraries Link to heading

Let’s explore what happens when we require a standard library:

# Discover what 'csv' adds to our namespace
before_constants = Object.constants
require 'csv'
after_constants = Object.constants

new_constants = after_constants - before_constants
puts "CSV library added: #{new_constants}"
CSV library added:
ScanError
SingleForwardable
StringIO
CSV
DateTime
StringScanner
Date
Forwardable

Explore what we found Link to heading

new_constants.each do |const|
  obj = Object.const_get(const)
  puts "\n=== Exploring #{const} ==="
  puts "Type: #{obj.class}"
  puts "Methods: #{obj.methods(false).first(10)}"
  
  if obj.is_a?(Module)
    puts "Constants: #{obj.constants.first(5)}"
  end
end

Basic Introspection Methods Link to heading

Discovering Available Methods Link to heading

The .methods method is your primary tool for discovering what an object can do. It returns an array of method names that the object responds to.

# Get all methods for a string
str = "hello world"
all_methods = str.methods
puts "String has #{all_methods.length} methods"

# Get only the object's own methods (not inherited)
own_methods = str.methods(false)
puts "String's own methods: #{own_methods}"
String has 182 methods
String's own methods: []

Method Filtering Techniques Link to heading

When dealing with hundreds of methods, filtering becomes essential:

str = "test string"

# Filter by pattern
array_methods = str.methods.grep(/array/)
puts "Array-related methods: #{array_methods}"

# Filter by return type (using respond_to?)
predicate_methods = str.methods.grep(/\?$/)
puts "Predicate methods: #{predicate_methods.first(10)}"

# Group methods by prefix
groups = str.methods.group_by { |m| m.to_s[0] }
puts "Methods starting with 'to_': #{groups['t'].select { |m| m.to_s.start_with?('to_') }}"
Array-related methods: []
Predicate methods: [:valid_encoding?, :ascii_only?, :unicode_normalized?, :include?, :empty?, :casecmp?, :match?, :eql?, :end_with?, :start_with?]

Getting Method Information Link to heading

Once you find interesting methods, use .method to get detailed information:

str = "example"

# Get method object
upcase_method = str.method(:upcase)
puts "Method object: #{upcase_method}"
puts "Method owner: #{upcase_method.owner}"
puts "Method arity: #{upcase_method.arity}"
puts "Method parameters: #{upcase_method.parameters}"

# Get source location (if available)
begin
  puts "Source location: #{upcase_method.source_location}"
rescue
  puts "Source location not available (likely C implementation)"
end
Method object: #<Method: String#upcase(*)>
Method owner: String
Method arity: -1
Method parameters: [[:rest]]
Source location not available (likely C implementation)

Class and Module Introspection Link to heading

Understanding Inheritance Link to heading

Use .ancestors to understand the inheritance chain and included modules:

# String's inheritance chain
puts "String ancestors: #{String.ancestors}"

# Array's inheritance chain  
puts "Array ancestors: #{Array.ancestors}"

# Custom class example
class MyClass
  include Enumerable
end

puts "MyClass ancestors: #{MyClass.ancestors}"
String ancestors: [String, Comparable, Object, Kernel, BasicObject]
Array ancestors: [Array, Enumerable, Object, Kernel, BasicObject]
MyClass ancestors: [MyClass, Enumerable, Object, Kernel, BasicObject]

Module vs Class Exploration Link to heading

Different approaches for modules vs classes:

# Module exploration
module MyModule
  CONSTANT = 42
  
  def self.module_method
    "module method"
  end
  
  def instance_method
    "instance method"
  end
end

puts "MyModule constants: #{MyModule.constants}"
puts "MyModule methods: #{MyModule.methods(false)}"
puts "MyModule instance methods: #{MyModule.instance_methods(false)}"
MyModule constants: [:CONSTANT]
MyModule methods: [:module_method]
MyModule instance methods: [:instance_method]

Class exploration Link to heading

class MyClass
  CLASS_CONSTANT = "class constant"
  
  def self.class_method
    "class method"
  end
  
  def instance_method
    "instance method"
  end
end

puts "MyClass constants: #{MyClass.constants}"
puts "MyClass methods: #{MyClass.methods(false)}"
puts "MyClass instance methods: #{MyClass.instance_methods(false)}"
puts "MyClass superclass: #{MyClass.superclass}"
MyClass constants: [:CLASS_CONSTANT]
MyClass methods: [:class_method]
MyClass instance methods: [:instance_method]
MyClass superclass: Object

Method Introspection Link to heading

Discovering Available Methods Link to heading

Different ways to discover methods:

class Example
  def public_method; end
  
  private
  
  def private_method; end
  
  protected
  
  def protected_method; end
end

obj = Example.new

# All methods the object responds to
puts "All methods: #{obj.methods.length}"

# Only public methods
puts "Public methods: #{obj.public_methods.length}"

# Only private methods (usually not accessible directly)
puts "Private methods: #{obj.private_methods.length}"

# Only protected methods
puts "Protected methods: #{obj.protected_methods.length}"
All methods: 52
Public methods: 51
Private methods: 74
Protected methods: 1

Filtering and Exploring Methods Link to heading

Advanced filtering techniques:

str = "test"

# Get methods by parameter count
no_params = str.methods.select { |m| str.method(m).arity == 0 }
one_param = str.methods.select { |m| str.method(m).arity == 1 }
puts "Methods with no parameters: #{no_params.first(10)}"
puts "Methods with one parameter: #{one_param.first(10)}"
Methods with no parameters: [:encoding, :each_grapheme_cluster, :valid_encoding?, :ascii_only?, :b, :dup, :to_r, :to_c, :+@, :-@]
Methods with one parameter: [:force_encoding, :%, :*, :+, :partition, :<=>, :<<, :==, :===, :=~]

Get methods by parameter type Link to heading

keyword_params = str.methods.select do |m|
  str.method(m).parameters.any? { |type, _| type == :key }
end
puts "Methods with keyword parameters: #{keyword_params.first(5)}"
Methods with keyword parameters: [:unpack, :unpack1, :clone]

Getting Method Information Link to heading

Detailed method analysis:

# Analyze a specific method
method_obj = "test".method(:gsub)

puts "Method name: #{method_obj.name}"
puts "Method owner: #{method_obj.owner}"
puts "Method arity: #{method_obj.arity}"
puts "Method parameters: #{method_obj.parameters}"

# Check if method is from Ruby core or C extension
if method_obj.source_location
  puts "Ruby implementation: #{method_obj.source_location}"
else
  puts "C implementation (no source available)"
end
Method name: gsub
Method owner: String
Method arity: -1
Method parameters: [[:rest]]
C implementation (no source available)

Object Introspection Link to heading

Basic Object Information Link to heading

Every object has fundamental introspection capabilities:

obj = "hello world"

# Basic object information
puts "Object class: #{obj.class}"
puts "Object class name: #{obj.class.name}"
puts "Object ID: #{obj.object_id}"
puts "Object hash: #{obj.hash}"

# Check object capabilities
puts "Responds to upcase?: #{obj.respond_to?(:upcase)}"
puts "Responds to non_existent?: #{obj.respond_to?(:non_existent)}"
puts "Is a kind of String?: #{obj.is_a?(String)}"
puts "Is a kind of Object?: #{obj.is_a?(Object)}"
Object class: String
Object class name: String
Object ID: 16
Object hash: -2739464589213975576
Responds to upcase?: true
Responds to non_existent?: false
Is a kind of String?: true
Is a kind of Object?: true

Dynamic Method Discovery Link to heading

Discover methods at runtime:

# Get all methods that return strings
str = "test"
string_returning = str.methods.select do |method|
  begin
    result = str.send(method) if str.method(method).arity == 0
    result.is_a?(String)
  rescue
    false
  end
end

puts "Methods returning strings: #{string_returning.first(10)}"

# Find methods that don't modify the object (non-bang methods)
non_bang_methods = str.methods.reject { |m| m.to_s.end_with?('!') }
puts "Non-destructive methods: #{non_bang_methods.first(10)}"
Methods returning strings: [:b, :dup, :+@, :-@, :next, :succ!, :next!, :chr, :clear, :dedup]
Non-destructive methods: [:encoding, :each_grapheme_cluster, :slice, :valid_encoding?, :ascii_only?, :force_encoding, :b, :dup, :unicode_normalized?, :to_r]

Exploring Different Object Types Link to heading

Different object types have different introspection patterns:

# String introspection
str = "hello"
puts "String singleton methods: #{str.singleton_methods}"
puts "String instance variables: #{str.instance_variables}"

# Array introspection
arr = [1, 2, 3]
puts "Array length: #{arr.length}"
puts "Array methods: #{arr.methods.grep(/each/).first(5)}"

# Hash introspection
hash = { key: "value" }
puts "Hash keys method: #{hash.method(:keys)}"
puts "Hash parameters: #{hash.method(:keys).parameters}"

# Custom object introspection
class CustomClass
  attr_accessor :instance_var
  
  def initialize
    @instance_var = "value"
  end
end

custom = CustomClass.new
puts "Custom class instance variables: #{custom.instance_variables}"
puts "Custom class instance variable values: #{custom.instance_variables.map { |v| [v, custom.instance_variable_get(v)] }.to_h}"
String singleton methods: []
String instance variables: []
Array length: 3
Array methods: [:each_index, :reverse_each, :each, :each_with_index, :each_entry]
Hash keys method: #<Method: Hash#keys()>
Hash parameters: []
Custom class instance variables: [:@instance_var]
Custom class instance variable values: {:@instance_var=>"value"}

Advanced Introspection Techniques Link to heading

Using Send for Private Methods Link to heading

Access private methods for complete introspection:

class PrivateExample
  def public_method
    "public"
  end
  
  private
  
  def private_method
    "private"
  end
  
  def method_with_args(arg1, arg2)
    "args: #{arg1}, #{arg2}"
  end
end

obj = PrivateExample.new

# Normal access fails
begin
  obj.private_method
rescue NoMethodError => e
  puts "Normal access failed: #{e.message}"
end

# Using send to access private methods
puts "Send access: #{obj.send(:private_method)}"
puts "Send with args: #{obj.send(:method_with_args, "arg1", "arg2")}"
Normal access failed: private method 'private_method' called for an instance of PrivateExample
Send access: private
Send with args: args: arg1, arg2

Method Missing Exploration Link to heading

Understanding dynamic method generation:

class MethodMissingExample
  def method_missing(method_name, *args)
    if method_name.to_s.start_with?('dynamic_')
      "Dynamic method called: #{method_name} with args: #{args}"
    else
      super
    end
  end
  
  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.start_with?('dynamic_') || super
  end
end

obj = MethodMissingExample.new

# Test dynamic method handling
puts "Responds to dynamic_test?: #{obj.respond_to?(:dynamic_test)}"
puts "Calling dynamic_test: #{obj.dynamic_test("arg1", "arg2")}"
puts "Responds to unknown?: #{obj.respond_to?(:unknown)}"
Responds to dynamic_test?: true
Calling dynamic_test: Dynamic method called: dynamic_test with args: ["arg1", "arg2"]
Responds to unknown?: false

Source Location Inspection Link to heading

Find where methods are defined:

# Ruby method with source location
class RubyMethod
  def ruby_method
    "Ruby implementation"
  end
end

ruby_obj = RubyMethod.new
ruby_method = ruby_obj.method(:ruby_method)
puts "Ruby method source: #{ruby_method.source_location}"

# String method (C implementation)
string_method = "test".method(:upcase)
puts "String method source: #{string_method.source_location}"

# Get all methods with source locations
methods_with_sources = ruby_obj.methods.select do |method|
  location = ruby_obj.method(method).source_location
  location && !location.first.include?('<internal:')
end

puts "Methods with Ruby sources: #{methods_with_sources}"
Ruby method source: ["-e", 3]
String method source: 
Methods with Ruby sources: [:ruby_method]

Exploring Object Relationships Link to heading

Understanding how objects relate to each other:

# Class relationships
puts "String's superclass: #{String.superclass}"
puts "String's included modules: #{String.included_modules}"
puts "String's ancestors: #{String.ancestors}"

# Object relationships
str1 = "hello"
str2 = "hello"

puts "Same object?: #{str1.object_id == str2.object_id}"
puts "Equal values?: #{str1 == str2}"
puts "Same class?: #{str1.class == str2.class}"

# Method ownership
class Base
  def base_method; end
end

class Derived < Base
  def derived_method; end
end

derived = Derived.new
puts "base_method owner: #{derived.method(:base_method).owner}"
puts "derived_method owner: #{derived.method(:derived_method).owner}"
base_method owner: Base
derived_method owner: Derived

Practical Example: Understanding Ruby 3.4 Standard Libraries Link to heading

Step 1: Understand the Main Module Link to heading

Let’s explore the CSV library as a practical example:

# Reset and discover CSV library
before_constants = Object.constants
require 'csv'
after_constants = Object.constants

csv_constants = after_constants - before_constants
puts "CSV added these constants: #{csv_constants}"

# Explore the main CSV module
csv_module = Object.const_get(:CSV)
puts "\n=== CSV Module Analysis ==="
puts "CSV module type: #{csv_module.class}"
puts "CSV module methods: #{csv_module.methods(false).first(10)}"
puts "CSV module constants: #{csv_module.constants.first(5)}"

Step 2: Create Instances and Explore Them Link to heading

# Create CSV objects and explore them
csv_data = "name,age,city\nJohn,30,NYC\nJane,25,LA"

# Parse CSV and explore the result
parsed = CSV.parse(csv_data)
puts "\n=== Parsed CSV Analysis ==="
puts "Parsed data class: #{parsed.class}"
puts "Parsed data methods: #{parsed.methods.grep(/each/).first(5)}"

# Explore CSV rows
first_row = parsed.first
puts "\n=== CSV Row Analysis ==="
puts "Row class: #{first_row.class}"
puts "Row methods: #{first_row.methods.select { |m| m.to_s.include?('include?') || m.to_s.include?('index') }.first(5)}"
Parsed data class: Array
Parsed data methods: [:each_index, :reverse_each, :each, :each_with_index, :each_entry]
Row class: Array
Row methods: [:each_index, :find_index, :bsearch_index, :include?, :index]

Step 3: Understand Key Methods from the Module Link to heading

# Explore key CSV methods
csv_methods = CSV.methods(false)
puts "\n=== Key CSV Methods ==="

csv_methods.each do |method|
  method_obj = CSV.method(method)
  puts "#{method}:"
  puts "  Arity: #{method_obj.arity}"
  puts "  Parameters: #{method_obj.parameters}"
  puts "  Source: #{method_obj.source_location ? 'Ruby' : 'C extension'}"
  puts
end
generate_line:
  Arity: -2
  Parameters: [[:req, :row], [:keyrest, :options]]
  Source: Ruby

filter:
  Arity: -1
  Parameters: [[:opt, :input], [:opt, :output], [:keyrest, :options]]
  Source: Ruby

generate_lines:
  Arity: -2
  Parameters: [[:req, :rows], [:keyrest, :options]]
  Source: Ruby

parse_line:
  Arity: -2
  Parameters: [[:req, :line], [:keyrest, :options]]
  Source: Ruby

Step 4: Try Using the Library Based on Our Discoveries Link to heading

# Use what we discovered to work with CSV
csv_string = "name,age\nAlice,25\nBob,30"

# Based on our discovery, we know CSV has a parse method
rows = CSV.parse(csv_string)
puts "Parsed #{rows.length} rows"

# We discovered rows are arrays, so we can use array methods
rows.each_with_index do |row, index|
  puts "Row #{index}: #{row.join(' - ')}"
end

# Let's discover CSV options
puts "\n=== CSV Options Discovery ==="
if CSV.respond_to?(:instance_variables)
  puts "CSV instance variables: #{CSV.instance_variables}"
end

# Try common CSV patterns based on our discoveries
begin
  # Try with headers (common CSV pattern)
  header_rows = CSV.parse(csv_string, headers: true)
  puts "With headers: #{header_rows.first.class}"
rescue => e
  puts "Headers option failed: #{e.message}"
end
Parsed 1 rows
Row 0: name - age\nAlice - 25\nBob - 30
With headers: NilClass

Troubleshooting Common Issues Link to heading

Dynamically Generated Methods Link to heading

Some libraries generate methods dynamically:

# Example of method generation
class DynamicMethods
  def self.define_method_prefix(prefix)
    define_method("#{prefix}_test") { "#{prefix} test" }
    define_method("#{prefix}_example") { "#{prefix} example" }
  end
end

DynamicMethods.define_method_prefix(:dynamic)
obj = DynamicMethods.new

# These methods won't show up in initial inspection
puts "All methods: #{obj.methods.grep(/^dynamic/)}"

# But they exist and can be called
puts "dynamic_test result: #{obj.dynamic_test}"
All methods: [:dynamic_example, :dynamic_test]

Handling Method Missing Link to heading

When libraries use method_missing extensively:

class MethodMissingHeavy
  def method_missing(method_name, *args, &block)
    if method_name.to_s.match(/^find_by_(.+)$/)
      attribute = $1
      "Finding by #{attribute} with args: #{args}"
    else
      super
    end
  end
  
  def respond_to_missing?(method_name, include_private = false)
    method_name.to_s.match(/^find_by_(.+)$/) || super
  end
end

obj = MethodMissingHeavy.new

# These methods don't exist in normal inspection
puts "find_by_name in methods?: #{obj.methods.include?(:find_by_name)}"
puts "respond_to?: #{obj.respond_to?(:find_by_name)}"

# But they work when called
puts "Result: #{obj.find_by_name('John')}"

Dealing with C Extensions Link to heading

Some methods are implemented in C and have limited introspection:

# String methods are mostly C extensions
str = "test"
c_methods = str.methods.select do |method|
  str.method(method).source_location.nil?
end

puts "C extension methods: #{c_methods.first(10)}"

# Ruby methods have source locations
ruby_methods = str.methods.select do |method|
  location = str.method(method).source_location
  location && !location.first.include?('<internal:')
end

puts "Ruby methods: #{ruby_methods}"

External Tools for Gem Discovery Link to heading

Using ri (Ruby Index) Link to heading

The ri tool provides documentation for Ruby methods:

# Get documentation for String methods
ri String#upcase
ri String#gsub

# Get documentation for entire class
ri String
ri Array

# Search for methods
ri -f upcase

Using irb for Interactive Exploration Link to heading

# Start irb and explore interactively
# irb

# Once in irb:
# > require 'nokogiri'
# > Nokogiri.methods(false)
# > doc = Nokogiri::HTML("<div>test</div>")
# > doc.methods.grep(/css/)

Using pry for Enhanced Introspection Link to heading

# Install pry if needed: gem install pry

# Start pry: pry

# Pry provides enhanced introspection:
# > require 'csv'
# > ls CSV          # List methods and constants
# > show-source CSV.parse  # Show method source
# > cd CSV          # Change context to CSV
# > self.methods    # Methods in current context

Finding and Using Online Documentation Link to heading

Finding GitHub Repository Link to heading

Most gems are hosted on GitHub:

# Find gem source on GitHub
# 1. Go to rubygems.org/gem_name
# 2. Look for "Source Code" link
# 3. Or search: github.com search for gem_name ruby

Using RubyGems.org Link to heading

# Get gem information
gem list nokogiri --remote
gem specification nokogiri

# View gem documentation
gem server  # Starts local documentation server
# Visit http://localhost:8808/

RubyDoc.info Link to heading

Comprehensive Ruby documentation site:

# Visit rubydoc.info
# Search for: nokogiri
# Browse: rubydoc.info/gems/nokogiri

Working with the Right Version Link to heading

Checking Your Current Version Link to heading

# Check Ruby version
puts "Ruby version: #{RUBY_VERSION}"
puts "Ruby patch level: #{RUBY_PATCHLEVEL}"
puts "Ruby platform: #{RUBY_PLATFORM}"

# Check gem versions
begin
  require 'nokogiri'
  puts "Nokogiri version: #{Nokogiri::VERSION}"
rescue LoadError
  puts "Nokogiri not installed"
end

Why Version Matters Link to heading

Different versions have different methods:

# Ruby 2.7 vs 3.0+ differences
if RUBY_VERSION >= '3.0'
  puts "Ruby 3.0+ has keyword argument separation"
else
  puts "Ruby 2.7 has positional/keyword argument compatibility"
end

# Check for method availability
if String.method_defined?(:delete_prefix)
  puts "String#delete_prefix available (Ruby 2.5+)"
else
  puts "String#delete_prefix not available"
end

Gem Version Differences Link to heading

# Different gem versions can have different APIs
begin
  require 'nokogiri'
  
  # Check nokogiri version-specific features
  if Nokogiri::VERSION >= '1.10'
    puts "Nokogiri 1.10+ features available"
  else
    puts "Using older Nokogiri version"
  end
  
rescue LoadError
  puts "Install nokogiri: gem install nokogiri"
end

The Danger of Outdated Tutorials Link to heading

# Example: Tutorial might show this (old way)
# result = Nokogiri::HTML("<div>test</div>")  # Old API

# But current version might require:
# result = Nokogiri::HTML4.parse("<div>test</div>")  # New API

# Always check current API:
begin
  require 'nokogiri'
  
  # Discover current API
  nokogiri_methods = Nokogiri.methods(false)
  puts "Current Nokogiri methods: #{nokogiri_methods}"
  
  # Find HTML-related methods
  html_methods = nokogiri_methods.select { |m| m.to_s.downcase.include?('html') }
  puts "HTML methods: #{html_methods}"
  
rescue LoadError
  puts "Nokogiri not available for API discovery"
end

Using the ‘gem’ command to find documentation Link to heading

The gem command provides several useful tools for discovering information about installed gems, which can be valuable when exploring unknown libraries.

Finding Gem Information Link to heading

The gem info command shows detailed information about a gem, including the homepage where you can find documentation:

gem info nokogiri

nokogiri (1.18.10, 1.18.9)
    Platforms:
    Authors: Mike Dalessio, Aaron Patterson, Yoko Harada, Akinori MUSHA,
    John Shahid, Karol Bucek, Sam Ruby, Craig Barnes, Stephen Checkoway,
    Lars Kanis, Sergio Arbeo, Timothy Elliott, Nobuyoshi Nakada
    Homepage: https://nokogiri.org
    License: MIT
    Installed at: /path/to/gems/nokogiri-1.18.10

    Nokogiri () makes it easy and painless to work with XML and HTML
    from Ruby.

The homepage URL is particularly valuable for finding official documentation, examples, and API references.

Other Useful Gem Commands Link to heading

  • gem outdated - Check which installed gems have newer versions available
  • gem update - Update all outdated gems to their latest versions

Discovering More Gem Commands Link to heading

The gem command has many more options for managing and exploring gems. Use gem --help to see all available commands and discover additional tools that might be useful for your introspection workflow.

This gem-level information complements Ruby’s built-in introspection methods, giving you both runtime discovery and documentation access.

Complete Gem Discovery Workflow Link to heading

Step 1: Initial Setup and Version Check Link to heading

# Check environment
puts "Ruby version: #{RUBY_VERSION}"
puts "Working directory: #{Dir.pwd}"

# Check if gem is available
begin
  gem 'nokogiri'
  puts "Nokogiri gem available"
  
  # Get gem information including homepage
  puts "Use 'gem info nokogiri' to find documentation and homepage"
rescue Gem::LoadError
  puts "Need to install: gem install nokogiri"
  exit
end

Step 2: Discovery Phase (Offline) Link to heading

# Discover what the gem adds
before = Object.constants
require 'nokogiri'
after = Object.constants

new_constants = after - before
puts "Nokogiri added: #{new_constants}"

# Explore each new constant
new_constants.each do |const|
  obj = Object.const_get(const)
  puts "\n=== #{const} ==="
  puts "Type: #{obj.class}"
  puts "Methods: #{obj.methods(false).first(10)}"
  
  if obj.is_a?(Module)
    puts "Constants: #{obj.constants.first(5)}"
  end
end

Step 3: Interactive Exploration (irb/pry) Link to heading

# In irb or pry:
# 1. Create instances
# doc = Nokogiri::HTML("<div>test</div>")

# 2. Explore methods
# doc.methods.grep(/css/)

# 3. Try common patterns
# doc.css('div')
# doc.at_css('div')
# doc.search('div')

Step 4: Documentation Lookup (Online) Link to heading

# 1. Check RubyGems.org for latest version
# 2. Find GitHub repository for examples
# 3. Check RubyDoc.info for API reference
# 4. Look for issues or discussions about usage

Step 5: Verification and Testing Link to heading

# Test your discoveries
require 'nokogiri'

html = "<div class='test'>Hello World</div>"
doc = Nokogiri::HTML(html)

# Verify CSS selector works
elements = doc.css('.test')
puts "Found #{elements.length} elements with class 'test'"
puts "Text content: #{elements.first.text}"

# Verify method discovery was correct
puts "Document responds to css?: #{doc.respond_to?(:css)}"
puts "Document responds to xpath?: #{doc.respond_to?(:xpath)}"
Found 1 elements with class 'test'
Text content: Hello World
Document responds to css?: true
Document responds to xpath?: true

Conclusion Link to heading

Ruby introspection is a powerful skill that enables you to understand and work with any Ruby library, even without documentation. By following a systematic approach—starting with namespace discovery, moving through method exploration, and using both built-in and external tools—you can quickly master unfamiliar libraries.

The key techniques to remember are:

  1. Start broad with .constants and .methods to get an overview
  2. Filter systematically to find relevant methods and capabilities
  3. Use .method to get detailed information about specific methods
  4. Leverage external tools like ri, irb, and pry for enhanced exploration
  5. Check versions to ensure you’re working with the right API
  6. Verify discoveries through practical testing

The combination of offline introspection, interactive exploration, and online documentation gives you a complete toolkit for understanding any Ruby library. These skills are especially valuable when working with poorly documented libraries, legacy code, or when you need to understand exactly how a library works under the hood.

Practice these techniques with different libraries, and you’ll develop an intuition for quickly understanding Ruby codebases and making the most of the rich introspection capabilities that Ruby provides.