Polyglot
Languages Ruby stdlib
Ruby § stdlib

Standard library

The Ruby standard library admits substantial breadth — file system, networking, date and time, JSON, YAML, CSV, regular expressions, cryptography, logging, configuration, plus the core classes (Array, Hash, String, Integer, Float, etc.). The library is layered: core (always available — Array, String, etc.), standard (loaded via requirejson, csv, set), bundled gems (shipped with Ruby but loaded as gems — pp, mutex_m), and the RubyGems ecosystem (third-party). The combination — a substantial core, a rich standard library, the conventional Bundled and Default gems, the discipline of require for the substantial features — is the substance of Ruby’s runtime library.

This tour points out the principal built-in classes and standard-library modules.

Core classes

The core classes are always available — no require needed:

String, Integer, Float, Numeric, Rational, Complex, BigDecimal
Symbol, Array, Hash, Range, Proc, Method
TrueClass, FalseClass, NilClass
Object, Class, Module
Comparable, Enumerable
File, IO, Dir, Pathname (loaded as needed)
Time, Date, DateTime
Regexp, MatchData
Exception, StandardError, RuntimeError, ...

Treated in Types, Strings, Data structures, Enumerable.

Object and Kernel

Object is the base class of (almost) all Ruby objects. Kernel is mixed into Object, providing global-style methods accessible everywhere:

puts "hello"                                      # Kernel#puts
print "hello"                                     # Kernel#print
p obj                                             # Kernel#p — debug print

require "json"                                    # Kernel#require
require_relative "lib/foo"
load "config.rb"

raise "error"                                     # Kernel#raise
catch(:label) { ... }                             # Kernel#catch
throw :label, value                               # Kernel#throw

at_exit { puts "goodbye" }                        # registers exit handler
exit                                              # Kernel#exit
exit!(0)                                          # without finalisers

sleep 1                                           # Kernel#sleep

Integer("42")                                     # strict conversion
Float("3.14")
String(123)
Array(nil)                                        # []
Array([1, 2])                                     # [1, 2]

The mechanism admits substantial convenience — puts, p, etc. are everywhere.

pp (pretty print)

Pretty-printed output:

require "pp"

complex = { name: "Alice", roles: ["admin", "user"], settings: { theme: "dark" } }
pp complex
# {:name=>"Alice",
#  :roles=>["admin", "user"],
#  :settings=>{:theme=>"dark"}}

Since Ruby 3.0, pp is loaded by default; explicit require "pp" is rarely needed.

File system: File, Dir, Pathname

File.read("file.txt")                             # whole file as string
File.write("file.txt", "content")                 # write
File.binread("file.bin")                          # binary read
File.exist?("file.txt")
File.directory?("path")
File.size("file.txt")
File.basename("/etc/hosts")                       # "hosts"
File.dirname("/etc/hosts")                        # "/etc"
File.extname("file.txt")                          # ".txt"
File.join("a", "b", "c")                          # "a/b/c"
File.expand_path("~/foo")                         # absolute path

File.foreach("file.txt") { |line| puts line }     # streaming line iteration

Dir.entries(".")                                  # all entries
Dir.glob("*.rb")                                  # ["foo.rb", "bar.rb"]
Dir["*.{rb,txt}"]                                  # alternative glob
Dir.mkdir("new_dir")
Dir.exist?("path")

Dir.chdir("/tmp") do
  # work in /tmp; returns to original dir at block end
end

require "pathname"
p = Pathname.new("/etc/hosts")
p.basename                                        # #<Pathname:hosts>
p.dirname                                         # #<Pathname:/etc>
p.exist?
p.read
p + "subdir"                                      # joined Pathname

Treated in I/O.

Time and Date

Time.now                                          # current time
Time.new(2026, 1, 15, 10, 0, 0)                   # specific
Time.parse("2026-01-15T10:00:00")                 # require "time"

Time.now.year
Time.now.month
Time.now.day
Time.now.hour
Time.now.to_i                                     # Unix epoch seconds

# Arithmetic:
Time.now + 60                                     # 60 seconds later
Time.now - 86400                                  # 1 day ago

# Formatting:
Time.now.strftime("%Y-%m-%d %H:%M:%S")            # "2026-01-15 10:00:00"
Time.now.iso8601

# Date:
require "date"
Date.today
Date.parse("2026-01-15")
Date.new(2026, 1, 15)

(Date.today + 7)                                  # one week later
(Date.today - Date.new(2026, 1, 1)).to_i          # days difference

DateTime.now                                       # combined date and time

For substantial date manipulation, the conventional alternatives:

  • ActiveSupport1.day.ago, 2.weeks.from_now, etc.
  • date-fns equivalents — minimal in Ruby.

JSON

require "json"

# Parse:
data = JSON.parse('{"name": "Alice", "age": 30}')
# => {"name"=>"Alice", "age"=>30}

JSON.parse(json_string, symbolize_names: true)
# => {:name=>"Alice", :age=>30}

# Generate:
JSON.dump({ name: "Alice", age: 30 })
# => '{"name":"Alice","age":30}'

JSON.pretty_generate(complex_data)
# pretty-printed output

# To/from object:
"hello".to_json                                   # "\"hello\""
[1, 2, 3].to_json                                 # "[1,2,3]"
{ a: 1 }.to_json                                  # "{\"a\":1}"

JSON.parse(File.read("data.json"))
File.write("out.json", JSON.pretty_generate(data))

YAML

require "yaml"

data = YAML.load_file("config.yml")
data = YAML.safe_load(File.read("config.yml"))    # safer; restricts class loading

YAML.dump(complex_data)
File.write("out.yml", YAML.dump(data))

The conventional contemporary discipline uses YAML.safe_load over YAML.load for untrusted input — load admits arbitrary class instantiation.

CSV

require "csv"

# Parse:
CSV.parse("a,b,c\n1,2,3")
# => [["a", "b", "c"], ["1", "2", "3"]]

CSV.parse(text, headers: true)
# => CSV::Table

# From file:
CSV.foreach("data.csv", headers: true) do |row|
  puts row["name"], row["age"]
end

# Write:
CSV.open("out.csv", "w") do |csv|
  csv << ["name", "age"]
  csv << ["Alice", 30]
  csv << ["Bob", 25]
end

# Generate to string:
csv_text = CSV.generate do |csv|
  csv << ["a", "b", "c"]
  csv << [1, 2, 3]
end

URI and Net::HTTP

require "uri"

uri = URI.parse("https://example.com/path?q=1&r=2")
uri.host                                          # "example.com"
uri.path                                          # "/path"
uri.query                                         # "q=1&r=2"

URI.encode_www_form(name: "Alice", age: 30)
# => "name=Alice&age=30"

URI.decode_www_form_component("Hello%20World")
# => "Hello World"

require "net/http"

# Simple GET:
response = Net::HTTP.get(URI("https://example.com"))

# With response object:
uri = URI("https://example.com/api")
response = Net::HTTP.get_response(uri)
response.code                                     # "200"
response.body

# POST with options:
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request["Content-Type"] = "application/json"
request.body = data.to_json
response = http.request(request)

For substantial HTTP clients, the conventional gem alternatives: Faraday, HTTParty, Typhoeus, httpx.

OpenStruct

Dynamic attribute objects:

require "ostruct"

o = OpenStruct.new(name: "Alice", age: 30)
o.name                                            # "Alice"
o.email = "a@b.c"                                 # admits setting any attribute
o.unknown                                         # nil

Conventional caveats: OpenStruct uses method_missing internally, admits substantial overhead. Struct or plain Hash is conventionally preferred for performance-sensitive code.

Logger

require "logger"

logger = Logger.new($stdout)                      # to stdout
logger = Logger.new("app.log")                    # to file
logger = Logger.new("app.log", 5, 10 * 1024 * 1024)  # rotate at 10 MB, keep 5

logger.level = Logger::INFO                       # DEBUG, INFO, WARN, ERROR, FATAL

logger.debug "debug message"
logger.info "info message"
logger.warn "warning"
logger.error "error"
logger.fatal "fatal"

logger.info("my_app") { "structured message" }    # with progname

# Custom format:
logger.formatter = ->(severity, datetime, progname, msg) {
  "[#{datetime}] #{severity}: #{msg}\n"
}

ENV and ARGV

ENV["HOME"]                                       # environment variable
ENV.fetch("DATABASE_URL")                         # raises if missing
ENV.fetch("PORT", "3000")                         # default
ENV["KEY"] = "value"                              # set
ENV.delete("KEY")
ENV.each { |k, v| puts "#{k}=#{v}" }

ARGV                                              # command-line arguments
                                                  # ruby script.rb foo bar
                                                  # ARGV = ["foo", "bar"]

For substantial CLI parsing, the conventional gems: OptionParser (standard), Thor, commander, dry-cli.

require "optparse"

options = {}
OptionParser.new do |parser|
  parser.banner = "Usage: my_script [options]"
  parser.on("-v", "--verbose", "Verbose output") { options[:verbose] = true }
  parser.on("-h", "--help") { puts parser; exit }
  parser.on("-n NAME", "--name=NAME", "Name to use") { |n| options[:name] = n }
end.parse!

puts options

Set

The Set class admits unique-value collections:

require "set"

s = Set.new([1, 2, 3])
s.add(4)
s << 5
s.include?(3)
s & other                                         # intersection
s | other                                         # union

Treated in Data structures.

Tempfile and tmpdir

require "tempfile"

Tempfile.create("prefix") do |f|
  f.write("temporary data")
  f.flush
  process(f.path)
end                                                # automatically cleaned up

require "tmpdir"
Dir.mktmpdir do |dir|
  # use dir; cleaned up at block end
end

FileUtils

Higher-level file operations:

require "fileutils"

FileUtils.mkdir_p("a/b/c")                        # like mkdir -p
FileUtils.rm_rf("dir")                            # like rm -rf
FileUtils.cp("src", "dest")
FileUtils.cp_r("src_dir", "dest_dir")
FileUtils.mv("old", "new")
FileUtils.touch("file.txt")
FileUtils.chmod(0644, "file.txt")
FileUtils.chmod_R(0755, "dir")

Open3 and process spawning

require "open3"

# Capture all:
stdout, stderr, status = Open3.capture3("ls", "-la")

# Streaming:
Open3.popen3("long_command") do |stdin, stdout, stderr, wait_thr|
  stdin.close
  stdout.each_line { |line| puts line }
  wait_thr.value
end

# Just standard output:
output = `ls -la`                                 # backtick form (kernel)
output = %x{ls -la}                                # alternative

system "command"                                  # returns true/false

Digest and securerandom

require "digest"

Digest::SHA256.hexdigest("hello")
# => "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

Digest::SHA1.hexdigest("hello")
Digest::MD5.hexdigest("hello")

require "securerandom"

SecureRandom.uuid                                  # UUID v4
SecureRandom.hex(16)                              # 32 hex chars
SecureRandom.base64(32)
SecureRandom.random_number(100)                   # 0..99
SecureRandom.random_bytes(16)                     # raw bytes

Base64

require "base64"

Base64.encode64("hello")                          # "aGVsbG8=\n"
Base64.strict_encode64("hello")                   # "aGVsbG8="
Base64.decode64("aGVsbG8=")                       # "hello"
Base64.urlsafe_encode64("hello")                  # URL-safe variant

Zlib and compression

require "zlib"

Zlib::Deflate.deflate("data to compress")
Zlib::Inflate.inflate(compressed)

# GZip:
Zlib::GzipReader.open("file.gz") do |gz|
  puts gz.read
end

Zlib::GzipWriter.open("file.gz") do |gz|
  gz.write("data")
end

Net::SMTP for email

require "net/smtp"

message = <<~MESSAGE
  From: sender@example.com
  To: recipient@example.com
  Subject: Hello

  Body of the message.
MESSAGE

Net::SMTP.start("smtp.example.com", 25) do |smtp|
  smtp.send_message(message, "sender@example.com", "recipient@example.com")
end

For substantial email handling, the conventional gem: Mail, Pony.

OptionParser for CLI

require "optparse"

options = {}
parser = OptionParser.new do |opts|
  opts.banner = "Usage: my_script.rb [options]"

  opts.on("-vLEVEL", "--verbose=LEVEL", "Set verbosity (1-5)") do |level|
    options[:verbose] = level.to_i
  end

  opts.on("-q", "--quiet", "Suppress output") do
    options[:quiet] = true
  end

  opts.on("-h", "--help", "Print help") do
    puts opts
    exit
  end
end

parser.parse!(ARGV)
puts options
puts ARGV                                         # remaining positional args

Test::Unit and Minitest

The standard testing frameworks:

require "minitest/autorun"

class TestExample < Minitest::Test
  def test_addition
    assert_equal 4, 2 + 2
  end

  def test_string
    assert "hello".include?("ll")
  end

  def test_raises
    assert_raises(ArgumentError) { do_invalid }
  end
end

For BDD-style testing, the conventional gem: RSpec.

Benchmark

require "benchmark"

Benchmark.bm do |x|
  x.report("a") { 1_000_000.times { something } }
  x.report("b") { 1_000_000.times { other_thing } }
end

# Or:
time = Benchmark.realtime { expensive_operation }
puts "took #{time.round(3)}s"

Forwardable

Method delegation:

require "forwardable"

class TodoList
  extend Forwardable

  def_delegators :@items, :each, :size, :empty?, :first, :last
  def_delegator :@items, :length, :count          # rename

  def initialize
    @items = []
  end

  def add(item)
    @items << item
  end
end

Treated in Modules and mixins.

Common patterns

Read JSON file

require "json"
data = JSON.parse(File.read("config.json"), symbolize_names: true)

Parse command-line arguments

require "optparse"

options = {}
OptionParser.new { |o|
  o.on("-v", "--verbose") { options[:verbose] = true }
}.parse!

run(ARGV, **options)

Generate a UUID

require "securerandom"
id = SecureRandom.uuid

Hash a string

require "digest"
Digest::SHA256.hexdigest("password")

Make HTTP request

require "net/http"
require "uri"
require "json"

uri = URI("https://api.example.com/data")
response = Net::HTTP.get(uri)
data = JSON.parse(response)

Run a subprocess

require "open3"
output, status = Open3.capture2("git", "status")
puts output if status.success?

Iterate over file lines

File.foreach("large.log") do |line|
  puts line if line.include?("ERROR")
end

Format a date

Time.now.strftime("%Y-%m-%d")                     # "2026-01-15"
Time.now.iso8601                                   # ISO 8601

A note on the conventional discipline

The contemporary Ruby standard-library advice:

  • Use the standard library before reaching for gems.
  • Use JSON for the conventional structured data.
  • Use YAML for configuration; safe_load for untrusted input.
  • Use CSV for tabular data.
  • Use URI and Net::HTTP for simple HTTP; gems for elaborate needs.
  • Use Pathname for path manipulation.
  • Use FileUtils for higher-level file operations.
  • Use Open3 for spawning subprocesses.
  • Use SecureRandom for cryptographic randomness.
  • Use Digest::SHA256 for hashing.
  • Use OptionParser for CLI argument parsing.
  • Use Logger for application logging.
  • Use Minitest (built-in) or RSpec (gem) for testing.

The combination — substantial core classes, the rich standard library, the conventional require for substantial features, the gem ecosystem layered on top — is the substance of Ruby’s runtime library. The discipline produces concise, expressive code with substantial built-in functionality; the conventional Ruby program leans heavily on the standard library.