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
- Basic Introspection Methods
- Class and Module Introspection
- Method Introspection
- Object Introspection
- Advanced Introspection Techniques
- Practical Example: Understanding Ruby 3.4 Standard Libraries
- Troubleshooting Common Issues
- External Tools for Gem Discovery
- Finding and Using Online Documentation
- Working with the Right Version
- Using the ‘gem’ command to find documentation
- Complete Gem Discovery Workflow
- Conclusion
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 availablegem 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:
- Start broad with
.constantsand.methodsto get an overview - Filter systematically to find relevant methods and capabilities
- Use
.methodto get detailed information about specific methods - Leverage external tools like
ri,irb, andpryfor enhanced exploration - Check versions to ensure you’re working with the right API
- 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.