Ruby 教程

Ruby 教程

Ruby 是一种简单快捷的面向对象脚本语言,由日本人松本行弘(Yukihiro Matsumoto)于 1995 年开发。Ruby 的设计哲学强调程序员的生产力与幸福感,遵循"最小惊讶原则"——即让代码的表现符合直觉,减少学习与使用中的意外。

本教程内容

本教程将从 Ruby 的基础语法开始,循序渐进地讲解变量、运算符、条件判断、循环、方法、类与对象、模块、字符串、数组、哈希等核心知识,再深入文件操作、异常处理、正则表达式、数据库访问、网络编程与多线程等进阶主题。

Ruby 的特点

  • 完全面向对象:一切皆对象,包括数字和布尔值
  • 简洁优雅的语法,接近自然语言
  • 动态类型,无需声明变量类型
  • 内置垃圾回收,无需手动管理内存
  • 丰富的标准库与活跃的社区生态(gems)

学习前提

建议具备一定的编程基础(任意语言),对 Web 开发感兴趣的话可以搭配 Ruby on Rails 一起学习。准备好 Ruby 环境后,就可以开始第一章的学习了。

Ruby 简介

Ruby 简介

Ruby 是一门开源的动态编程语言,专注于简洁与生产力。它的语法优雅而富有表达力,常用于 Web 开发(如知名的 Ruby on Rails 框架)、脚本编写、自动化任务与数据处理。

发展历史

1995 年,松本行弘发布了 Ruby 的第一个公开版本。其设计融合了 Perl 的实用、Smalltalk 的纯粹面向对象以及 Lisp 的函数式思想,形成了一种独特而富有魅力的语言风格。

Ruby 能做什么

  • Web 应用开发:Ruby on Rails、Sinatra 等框架
  • 自动化脚本与系统管理
  • 数据处理与文本解析
  • 原型开发与快速迭代
  • 运维工具(如 Chef、Puppet 的配置语言)

Hello World

puts "Hello, Ruby!"

# 输出: Hello, Ruby!

为什么学习 Ruby

Ruby 的代码可读性极高,几乎接近英语的自然表达。它鼓励开发者编写优雅、可维护的代码,是学习面向对象编程和函数式思想的绝佳入门语言。社区氛围友好,文档完善,学习资源丰富。

Ruby 环境

Ruby 环境

在开始编写 Ruby 代码之前,需要先在计算机上安装 Ruby 解释器。Ruby 支持主流操作系统:Linux、macOS 与 Windows。本节介绍环境准备的整体思路与常用工具。

环境构成

  • Ruby 解释器:执行 Ruby 代码的核心程序
  • gem 包管理器:安装与管理第三方库(RubyGems)
  • IRB:交互式命令行环境,适合快速试验代码
  • 文本编辑器或 IDE:编写代码的工具(如 VS Code、RubyMine)

版本选择

建议安装当前稳定的 Ruby 3.x 版本。旧项目可能需要特定版本,推荐使用版本管理工具:

  • Linux/macOS:rbenv 或 rvm
  • Windows:RubyInstaller

验证安装

ruby -v
# 输出示例: ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]

gem -v
# 输出示例: 3.4.6

第一个程序

# 保存为 hello.rb
puts "Hello, World!"

# 运行
# ruby hello.rb

环境就绪后,即可按照下一节的指引在具体操作系统上安装 Ruby。

Ruby 安装 - Linux

Ruby 安装 - Linux

Linux 系统安装 Ruby 有多种方式,推荐使用系统包管理器快速安装,或使用 rbenv/rvm 实现多版本管理。

方式一:系统包管理器

Debian/Ubuntu 系列:

sudo apt update
sudo apt install ruby-full

CentOS/RHEL/Fedora 系列:

sudo yum install ruby        # CentOS 7
sudo dnf install ruby        # Fedora / CentOS 8+

方式二:使用 rbenv(推荐)

rbenv 可以自由切换 Ruby 版本,适合开发环境:

# 安装依赖
sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libncurses5-dev libffi-dev libgdbm-dev

# 安装 rbenv
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

# 安装 ruby-build 插件
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

# 安装 Ruby
rbenv install 3.2.2
rbenv global 3.2.2

验证

ruby -v
gem -v

安装完成后,建议同时安装 bundler 用于管理项目依赖:gem install bundler。

Ruby 安装 - Windows

Ruby 安装 - Windows

Windows 上安装 Ruby 最便捷的方式是使用 RubyInstaller,它是一个官方推荐的安装包,内置 Ruby 与 MSYS2 开发环境。

安装步骤

  1. 访问 RubyInstaller 官网(rubyinstaller.org)下载最新版本安装包
  2. 选择与系统匹配的版本(64 位建议使用 x64)
  3. 运行安装程序,勾选"Add Ruby executables to your PATH"
  4. 安装完成后,安装程序会提示运行 ridk install,选择 1 安装 MSYS2 基础组件(用于编译原生扩展)

验证安装

ruby -v
# 输出示例: ruby 3.2.2-1 [x64-mingw-ucrt]

gem -v

在 Windows 上使用 Ruby

  • 命令提示符 / PowerShell:直接运行 ruby 文件名.rb
  • IRB:输入 irb 进入交互式环境
  • 编辑器:推荐 VS Code + Ruby 扩展

常见问题

  • 命令找不到:检查 PATH 是否包含 Ruby 安装目录
  • 原生扩展安装失败:确认 MSYS2 已通过 ridk install 安装

Windows 下部分 gem(如 eventmachine)需要编译环境,MSYS2 能解决绝大多数问题。

Ruby 命令行选项

Ruby 命令行选项

Ruby 解释器支持丰富的命令行选项,用于控制执行方式、调试级别与编码等行为。掌握这些选项可以更高效地使用 Ruby。

常用选项

选项说明
-e '代码'直接执行给定的 Ruby 代码
-c只检查语法,不执行
-w开启警告信息
-d开启调试模式(设置 $DEBUG)
-v显示版本号并进入详细模式
-I 路径添加库搜索路径
-r 库名执行前先 require 指定库
-K 编码指定源文件编码

使用示例

# 直接执行代码
ruby -e 'puts 1 + 2'

# 检查语法
ruby -c hello.rb

# 带警告运行
ruby -w hello.rb

# 引入 json 库后执行
ruby -r json -e 'puts JSON.generate({a: 1})'

调试相关

# 开启调试模式
ruby -d script.rb

调试模式下脚本会输出更多运行时信息,配合 binding.irb 或 byebug 可以在运行时打断点检查变量状态。

Ruby 环境变量

Ruby 环境变量

Ruby 运行时依赖一些环境变量来配置行为,例如库加载路径、gem 安装位置与编码设置等。本节介绍最常用的几个。

核心环境变量

变量作用
RUBYOPT启动时自动附加的命令行选项
RUBYLIB额外的库搜索路径(类似 -I)
GEM_HOMEgem 的安装目录
GEM_PATHgem 的搜索路径列表
BUNDLE_GEMFILE指定 Bundler 使用的 Gemfile
RUBY_YJIT_ENABLE开启 YJIT 即时编译器

在脚本中读取环境变量

# 读取
puts ENV["HOME"]
puts ENV["PATH"]

# 设置(仅影响当前进程)
ENV["MY_VAR"] = "hello"
puts ENV["MY_VAR"]

RUBYOPT 示例

# 让所有 Ruby 命令自动加载 warning 抑制
export RUBYOPT="-W0"

# 设置默认编码
export RUBYOPT="-Eutf-8"

查看当前环境

ruby -e 'ENV.each { |k, v| puts "#{k}=#{v}" }'

合理配置环境变量可以统一团队开发环境,避免编码、库路径不一致导致的问题。

Ruby 语法

Ruby 语法

Ruby 的语法以简洁著称:不需要分号结尾(可用换行分隔语句)、缩进不是语法要求(但强烈建议使用)、方法调用常可省略括号。本节快速浏览核心语法元素。

注释

# 单行注释

=begin
这是多行注释
=end

输出与输入

puts "输出并换行"
print "不换行"
printf("%d 和 %s\n", 100, "Ruby")

# 获取用户输入
name = gets.chomp
puts "你好, #{name}"

变量与常量

name = "Ruby"        # 局部变量
$global = 1          # 全局变量
@instance = 2        # 实例变量
@@class_var = 3      # 类变量
CONST = 100          # 常量(大写开头)

# 常量可被警告式重新赋值
CONST = 200

方法定义

def add(a, b)
  a + b   # 最后一行自动作为返回值
end

if 与 unless

x = 10
if x > 5
  puts "大"
end

puts "小" if x < 5     # 后置写法
puts "大" unless x < 5

Ruby 语法强调可读性,几乎每种控制结构都有简洁的替代写法,学习时注意体会这种"表达力"。

Ruby 类和对象

Ruby 类和对象

Ruby 是纯粹的面向对象语言,一切皆对象。类是对象的模板,对象是类的实例。本节介绍如何定义类、创建对象与访问属性。

定义类

class Person
  def initialize(name, age)
    @name = name   # 实例变量
    @age = age
  end

  def intro
    "我是 #{@name},今年 #{@age} 岁"
  end
end

创建对象

p1 = Person.new("小明", 18)
puts p1.intro   # 我是 小明,今年 18 岁

属性读写

class Person
  attr_reader :name      # 只读
  attr_writer :age       # 只写
  attr_accessor :city    # 读写
end

p = Person.new
p.city = "北京"
puts p.city

方法访问控制

class Demo
  def public_method; end

  private
  def secret; end

  protected
  def inner; end
end

继承

class Student < Person
  def intro
    super + ",是一名学生"
  end
end

类方法用 self.method 定义,单例方法可用 def obj.method 定义。Ruby 的类体系高度灵活,是学习 OOP 的优秀语言。

Ruby 类案例

Ruby 类案例

本节通过一个完整的案例——"银行账户管理系统",综合运用类、对象、属性访问、方法封装与继承等知识。

需求

实现一个 BankAccount 类:支持开户、存款、取款、查询余额;余额不足时抛出异常;并提供定期账户(SavingsAccount)子类,享有利率。

实现代码

class BankAccount
  attr_reader :owner, :balance

  def initialize(owner, balance = 0)
    @owner = owner
    @balance = balance
  end

  def deposit(amount)
    raise "金额必须为正" if amount <= 0
    @balance += amount
    self
  end

  def withdraw(amount)
    raise "余额不足" if amount > @balance
    @balance -= amount
    self
  end
end

class SavingsAccount < BankAccount
  def initialize(owner, balance = 0, rate = 0.02)
    super(owner, balance)
    @rate = rate
  end

  def add_interest
    @balance += (@balance * @rate).round(2)
  end
end

使用示例

acc = BankAccount.new("张三", 1000)
acc.deposit(500).withdraw(200)
puts acc.balance   # 1300

sav = SavingsAccount.new("李四", 10000)
sav.add_interest
puts sav.balance   # 10200.0

案例要点

  • attr_reader 提供只读访问
  • 方法链式调用:返回 self
  • 异常用于业务规则校验
  • 继承 + super 复用父类逻辑

这个小案例展示了 Ruby 面向对象的核心套路,掌握后可以应对更复杂的建模需求。

Ruby 变量

Ruby 变量

Ruby 变量根据作用域分为四种:局部变量、全局变量、实例变量与类变量。变量名首字符决定了其类型,这是 Ruby 的独特设计。

局部变量

name = "Ruby"        # 小写字母或下划线开头
age = 30
_local = true

局部变量在定义它的作用域内有效(方法、块、循环内)。

全局变量

$global = 10         # $ 开头,整个程序可见

def show
  puts $global
end

全局变量会破坏封装,应谨慎使用。Ruby 内置了一些特殊全局变量如 $stdin、$! (最近异常)。

实例变量

class Point
  def initialize(x, y)
    @x = x
    @y = y
  end

  def info
    "(#{@x}, #{@y})"
  end
end

实例变量以 @ 开头,属于单个对象,未初始化时为 nil。

类变量

class Counter
  @@count = 0

  def initialize
    @@count += 1
  end

  def self.count
    @@count
  end
end

类变量以 @@ 开头,被类及其所有实例共享。

常量与伪变量

MAX = 100          # 常量,大写开头
self               # 当前对象
nil / true / false # 伪变量

理解变量类型是掌握 Ruby 作用域规则的第一步,建议动手实验每种变量的可见范围。

Ruby 运算符

Ruby 运算符

Ruby 提供算术、比较、赋值、逻辑、位运算等丰富的运算符,并支持运算符重载(实际上运算符都是方法调用)。

算术运算符

a, b = 10, 3
puts a + b    # 13
puts a - b    # 7
puts a * b    # 30
puts a / b    # 3 (整数除法)
puts a % b    # 1 (取余)
puts a ** b   # 1000 (幂)

比较运算符

puts 1 == 1    # true (相等)
puts 1 != 2    # true
puts 2 > 1     # true
puts 1 <= 2    # true
puts 1 <=> 2   # -1 (宇宙飞船运算符:小于-1,等于0,大于1)
puts 1.eql?(1.0)  # false (类型也相同才 true)

赋值运算符

x = 5
x += 3    # x = 8
x -= 2    # x = 6
x *= 4    # x = 24
x /= 3    # x = 8
x %= 5    # x = 3
x **= 2   # x = 9

# 并行赋值
a, b = 1, 2
a, b = b, a   # 交换

逻辑运算符

true and false  # false (and / or 优先级低)
true && false   # false (&& / || 优先级高)
!true           # false
true || nil     # true

# 常用技巧:短路赋值
name ||= "默认值"    # 为空才赋值

三元与范围

x = 10
msg = x > 5 ? "大" : "小"

puts (1..5).to_a     # 包含结尾 [1,2,3,4,5]
puts (1...5).to_a    # 不含结尾 [1,2,3,4]

运算符优先级注意

and/or 优先级低于 &&/||,混用时建议加括号避免歧义。Ruby 还支持 defined? 与 === 等特殊运算符,后者在 case 语句中扮演重要角色。

Ruby 注释

Ruby 注释

注释是写给开发者看的说明文字,不会被执行。良好的注释能显著提升代码可维护性,Ruby 支持单行、多行以及文档注释三种形式。

单行注释

# 这是单行注释
puts "Hello"  # 行尾注释

多行注释

=begin
这是多行注释的第一行
第二行
=end

puts "继续执行"

文档注释(RDoc)

Ruby 社区习惯用文档注释生成 API 文档:

# 计算两数之和
#
# == 参数
# a, b :: 两个数值
# == 返回值
# 返回 a + b 的结果
def add(a, b)
  a + b
end

注释规范建议

  • 解释"为什么"而不是"是什么"(代码本身说明怎么做的)
  • 复杂算法标注思路与复杂度
  • TODO/FIXME 标记待办事项
  • 方法/类的文档注释保持简洁一致

用注释调试

# 临时注释掉某行排查问题
# puts result

# 标记未完成
# TODO: 支持负数输入

注意:=begin/=end 必须顶格书写,前面不能有空格,否则会被当作普通字符串处理。

Ruby 条件判断

Ruby 条件判断

Ruby 提供 if、unless、case 三种条件判断结构,每种都有后置写法,让代码更加简洁自然。

if / elsif / else

score = 85
if score >= 90
  puts "优秀"
elsif score >= 60
  puts "及格"
else
  puts "不及格"
end

后置 if

puts "成年" if age >= 18

# 一行三目
status = age >= 18 ? "成年" : "未成年"

unless(除非)

unless user.nil?
  puts "用户存在"
end

# 后置写法
puts "禁止访问" unless logged_in?

case / when

day = "周一"
case day
when "周六", "周日"
  puts "休息日"
when "周一".."周五"
  puts "工作日"
else
  puts "未知"
end

case 使用 === 进行匹配,支持范围、正则、类等多种匹配方式,非常灵活。

条件表达式注意

# 在 Ruby 中只有 nil 和 false 为假,0 和空字符串都是真
puts "真" if 0        # 输出 真
puts "真" if ""       # 输出 真
puts "假" if nil      # 不输出

理解 Ruby 的"真值"规则(仅 nil/false 为假)对编写条件判断至关重要。

Ruby 循环

Ruby 循环

Ruby 提供了 while、until、for 以及基于迭代器的 each、times、loop 等多种循环方式,推荐优先使用迭代器风格。

while 循环

i = 0
while i < 5
  puts i
  i += 1
end

until 循环

i = 0
until i >= 5
  puts i
  i += 1
end

for 循环

for i in 0..4
  puts i
end

迭代器(推荐)

5.times { |i| puts i }

(1..5).each { |n| puts n }

["a", "b", "c"].each_with_index do |v, idx|
  puts "#{idx}: #{v}"
end

控制语句

(1..10).each do |n|
  next if n.even?      # 跳过偶数
  break if n > 7       # 大于7退出
  puts n               # 输出 1 3 5 7
end

loop 与 retry

count = 0
loop do
  count += 1
  puts count
  break if count >= 3
end

Ruby 中 each、map 等迭代器比 for 更符合习惯,配合块(block)使用是 Ruby 编程的核心模式。

Ruby 方法

Ruby 方法

方法是 Ruby 中组织逻辑的基本单元。Ruby 的方法支持默认参数、可变参数、块参数、关键字参数等丰富特性,最后一条表达式的值自动作为返回值。

基本定义

def greet(name)
  "Hello, #{name}!"
end

puts greet("Ruby")   # Hello, Ruby!

# 调用时可省略括号
puts greet "Ruby"

返回值

def max(a, b)
  a > b ? a : b     # 最后一行自动返回
end

默认参数与可变参数

def power(x, n = 2)
  x ** n
end

def sum(*nums)
  nums.sum
end

puts power(3)          # 9
puts sum(1, 2, 3, 4)   # 10

关键字参数

def create_user(name:, age: 18, admin: false)
  { name: name, age: age, admin: admin }
end

create_user(name: "张三", admin: true)

块参数与 yield

def repeat(times)
  times.times { yield }
end

repeat(3) { puts "hi" }

方法别名与可见性

alias old_max max

def max(a, b)
  puts "调用中"
  old_max(a, b)
end

方法名可包含 ?、!、= 等后缀,如 empty?、sort!,用于表达语义约定。

Ruby 块

Ruby 块(Block)

块是 Ruby 最具特色的语法之一:一段可以传递给方法的代码片段,用 { } 或 do...end 包裹。块不是对象,但可以转换为 Proc 对象。

基本用法

[1, 2, 3].each { |n| puts n * 2 }

[1, 2, 3].each do |n|
  puts n * 2
end

约定:单行用 { },多行用 do...end。

yield 调用块

def my_each(arr)
  arr.each { |x| yield x }
end

my_each([1, 2]) { |x| puts "值: #{x}" }

块参数 block_given?

def log(msg)
  if block_given?
    yield msg
  else
    puts msg
  end
end

Proc 与 Lambda

p = Proc.new { |x| x * 2 }
l = lambda { |x| x * 2 }

puts p.call(5)   # 10
puts l.call(5)   # 10

区别:lambda 检查参数个数,return 只退出 lambda;Proc 不检查参数,return 会退出整个方法。

块转 Proc 与符号调用

# 用 & 捕获块
def capture(&block)
  block
end
b = capture { puts "hi" }
b.call

# 符号转块(常用技巧)
["a", "b"].map(&:upcase)   # ["A", "B"]

块是迭代器模式的基石,理解块与 Proc/lambda 的区别是进阶 Ruby 的关键。

Ruby 模块(Module)

Ruby 模块(Module)

模块在 Ruby 中有两大作用:作为命名空间组织方法,以及作为 Mixin 向类混入方法。Ruby 不支持多继承,模块提供了类似的能力。

定义模块

module MathUtils
  PI = 3.14159

  def self.square(x)
    x * x
  end

  def area
    self.class::PI
  end
end

puts MathUtils.square(4)   # 16

作为命名空间

module Payment
  class Alipay
    def pay; "支付宝支付"; end
  end

  class Wechat
    def pay; "微信支付"; end
  end
end

Payment::Alipay.new.pay

Mixin 混入

module Greetable
  def hello
    "你好,我是 #{name}"
  end
end

class Person
  include Greetable   # 实例方法
  attr_reader :name
  def initialize(name); @name = name; end
end

class Team
  extend Greetable    # 类方法
  def self.name; "团队"; end
end

Person.new("小明").hello
Team.hello

include vs prepend vs extend

  • include:模块方法成为实例方法(插入继承链中间)
  • prepend:插入继承链最前面,可覆盖类方法
  • extend:模块方法成为类方法

模块嵌套与 require

module A
  module B
    class C
    end
  end
end
A::B::C.new

模块还可以使用 module_function 同时提供实例与模块方法,是工具类的常见模式。

Ruby 字符串(String)

Ruby 字符串(String)

字符串是 Ruby 中使用最频繁的数据类型。Ruby 字符串可变,支持单双引号、插值、编码处理与丰富的方法库。

创建字符串

s1 = "双引号"
s2 = '单引号'
s3 = %q(不插值)
s4 = %Q(插值 #{1 + 1})
s5 = <<~EOF
  多行字符串
  heredoc 语法
EOF

插值与转义

name = "Ruby"
puts "Hello #{name}"     # 双引号支持插值
puts 'Hello #{name}'     # 单引号原样输出

常用方法

s = "Hello, Ruby"
s.length          # 11
s.upcase          # "HELLO, RUBY"
s.downcase        # "hello, ruby"
s.capitalize      # "Hello, ruby"
s.strip           # 去首尾空白
s.include?("Ruby")   # true
s.start_with?("He")  # true
s.split(", ")     # ["Hello", "Ruby"]
s.gsub("Ruby", "World")  # 替换

# 拼接
"a" + "b"         # "ab"
"a" * 3           # "aaa"

索引与切片

s = "Ruby"
s[0]        # "R"
s[0, 2]     # "Ru"
s[-1]       # "y"
s[1..2]     # "ub"

编码处理

s = "中文"
s.encoding           # UTF-8
s.bytesize           # 6 (UTF-8 下一个中文3字节)
s.length             # 2 (字符数)
s.encode("GBK")      # 转编码

Ruby 3 默认 UTF-8 编码,处理中文等 Unicode 字符时按字符操作,非常友好。

Ruby 数组(Array)

Ruby 数组(Array)

Ruby 数组是有序、可变的元素集合,可以混合存放任意类型,支持丰富的查询与变换方法,是日常开发的主力数据结构。

创建数组

arr1 = [1, 2, 3]
arr2 = %w[a b c]          # 字符串数组 ["a","b","c"]
arr3 = Array.new(3)       # [nil, nil, nil]
arr4 = Array.new(3, 0)    # [0, 0, 0]

访问元素

a = [10, 20, 30, 40]
a[0]        # 10
a[-1]       # 40
a.first     # 10
a.last      # 40
a[1, 2]     # [20, 30]
a[1..2]     # [20, 30]

增删元素

a = [1, 2]
a.push(3)        # [1,2,3] 末尾添加
a << 4           # [1,2,3,4] 语法糖
a.unshift(0)     # [0,1,2,3,4] 头部添加
a.pop            # 删除末尾
a.shift          # 删除头部
a.delete(2)      # 删除指定值
a.uniq!          # 去重(就地修改)

遍历与变换

[1, 2, 3].each { |n| puts n }
[1, 2, 3].map { |n| n * 2 }        # [2,4,6]
[1, 2, 3, 4].select(&:even?)       # [2,4]
[1, 2, 3, 4].reject(&:even?)       # [1,3]
[1, 2, 3].reduce(0) { |sum, n| sum + n }  # 6
[3, 1, 2].sort                     # [1,2,3]
[1, 2, 3].reverse                  # [3,2,1]

常用查询

a = [1, 2, 3]
a.size          # 3
a.empty?        # false
a.include?(2)   # true
a.max           # 3
a.sum           # 6
a.join("-")     # "1-2-3"

数组方法大多有带 ! 的就地版本(map!、sort!),不修改原数组的版本更安全,按需选择。

Ruby 哈希(Hash)

Ruby 哈希(Hash)

哈希(Hash)是键值对的集合,类似于其他语言的字典/映射。Ruby 哈希键值顺序保持插入顺序,Ruby 1.9+ 默认有序。

创建哈希

h1 = { "name" => "Ruby", "age" => 30 }
h2 = { name: "Ruby", age: 30 }    # 符号键简写
h3 = Hash.new               # 默认值为 nil
h4 = Hash.new(0)            # 默认值为 0

访问与修改

h = { name: "Ruby" }
h[:name]        # "Ruby"
h[:age] = 30    # 新增/修改
h.fetch(:age)   # 30,键不存在抛异常
h.fetch(:x, 0)  # 0,提供默认值
h.key?(:name)   # true

遍历

h = { a: 1, b: 2 }
h.each { |k, v| puts "#{k}=#{v}" }
h.each_key { |k| puts k }
h.each_value { |v| puts v }
h.map { |k, v| "#{k}:#{v}" }

变换与合并

h1 = { a: 1, b: 2 }
h2 = { b: 3, c: 4 }
h1.merge(h2)        # {a:1, b:3, c:4}
h1.keys             # [:a, :b]
h1.values           # [1, 2]
h1.select { |k, v| v > 1 }   # {b: 2}

# 默认值块
h = Hash.new { |hash, key| hash[key] = [] }
h[:users] << "张三"

符号键 vs 字符串键

h = { name: "Ruby" }
h[:name]          # "Ruby"
h["name"]         # nil (不同键)

# 从外部数据(JSON)解析来的哈希是字符串键
require "json"
data = JSON.parse('{"name":"Ruby"}')
data["name"]      # "Ruby"

哈希配合数组是 Ruby 数据处理的基础,each、map、select 等方法的组合几乎可以完成所有数据变换需求。

Ruby 日期 & 时间

Ruby 日期 & 时间

Ruby 通过内置的 Time 类和标准库 Date 提供日期时间处理能力。Time 处理时间戳与时刻,Date 处理日历日期,DateTime 两者兼顾。

Time 基本用法

require "time"

t = Time.now
puts t                    # 2026-08-17 10:30:00 +0800
t.year                    # 2026
t.month                   # 8
t.day                     # 17
t.hour                    # 10
t.strftime("%Y-%m-%d %H:%M")   # 格式化输出

# 创建指定时间
t2 = Time.new(2026, 1, 1, 0, 0, 0)
t3 = Time.at(1700000000)   # 时间戳转换

日期运算

t = Time.now
t + 3600        # 一小时后
t - 86400       # 一天前
t2 - t          # 时间差(秒)

Date 类

require "date"

d = Date.today
d.year                 # 2026
d + 1                  # 明天
d - 7                  # 一周前
d.leap?                # 是否闰年
Date.new(2026, 12, 25) # 创建日期
Date.parse("2026-08-17")   # 字符串解析

格式化与解析

require "time"

# 格式化
Time.now.strftime("%Y年%m月%d日 %H:%M")

# 解析
Time.parse("2026-08-17 10:30:00")
Time.iso8601("2026-08-17T10:30:00+08:00")

时区处理

t = Time.now
t.utc                # 转 UTC
t.localtime          # 转本地
t.zone               # 时区名
t.utc_offset         # 与 UTC 的秒差

注意:require "time" 提供扩展方法,Date 类需要 require "date"。商业应用中建议统一使用 UTC 存储、展示时转本地时区。

Ruby 范围(Range)

Ruby 范围(Range)

范围(Range)表示一个区间,例如 1..10。Ruby 范围可用于序列生成、条件判断、数组切片等场景,语法简洁而强大。

创建范围

r1 = 1..5      # 包含结尾 1,2,3,4,5
r2 = 1...5     # 不含结尾 1,2,3,4
r3 = "a".."e"  # 字符范围
r4 = 1.0..2.0  # 浮点范围

范围转数组

(1..5).to_a        # [1,2,3,4,5]
(1...5).to_a       # [1,2,3,4]
("a".."e").to_a    # ["a","b","c","d","e"]
(1..5).to_a.reverse

遍历与步长

(1..10).each { |n| print n }     # 12345678910
(0..10).step(2) { |n| print n }  # 0246810

成员判断

(1..5).include?(3)   # true
(1...5).cover?(4.5)  # true (范围包含该值)
(1..5) === 3         # true (case 中常用)

用于数组切片

arr = ["a", "b", "c", "d"]
arr[1..2]     # ["b","c"]
arr[1...3]    # ["b","c"]
arr[0..-1]    # 全部

case 与范围

score = 85
case score
when 90..100 then "A"
when 80...90 then "B"
when 70...80 then "C"
else "D"
end   # "B"

无起点/终点范围(Ruby 2.6+)

arr = [10, 20, 30, 40]
arr[2..]      # [30, 40]
arr[..1]      # [10, 20]

范围在 Ruby 中扮演重要角色:配合 case 做分级判断、配合数组做切片、配合 step 做步进遍历,是提升代码简洁度的利器。

Ruby 迭代器

Ruby 迭代器

迭代器是 Ruby 的"一等公民"设计:几乎所有容器(数组、哈希、范围)都通过迭代器方法进行遍历,配合块(block)形成优雅的循环模式。

each 系列

[1, 2, 3].each { |n| puts n }

{ a: 1, b: 2 }.each { |k, v| puts "#{k}=#{v}" }

# 带索引
["a", "b"].each_with_index { |v, i| puts "#{i}:#{v}" }
["a", "b"].each_index { |i| puts i }

# 连续多个元素
(1..10).each_slice(3) { |slice| p slice }
# [1,2,3] [4,5,6] [7,8,9] [10]

times / upto / downto

3.times { puts "hi" }
1.upto(5) { |n| print n }      # 12345
5.downto(1) { |n| print n }    # 54321

变换类迭代器

[1, 2, 3].map { |n| n * 2 }        # 变换
[1, 2, 3, 4].select(&:even?)        # 过滤
[1, 2, 3].reduce(0, :+)             # 归约 = 6
[1, 2, 3].inject { |sum, n| sum + n }

# 链式
(1..10).select(&:even?).map { |n| n * n }   # [4,16,36,64,100]

group_by / partition

["apple", "banana", "avocado"].group_by { |w| w[0] }
# {"a"=>["apple","avocado"], "b"=>["banana"]}

[1, 2, 3, 4].partition(&:even?)
# [[2,4], [1,3]]

自定义迭代器

class Range
  def each_square
    each { |n| yield n * n }
  end
end

(1..3).each_square { |sq| puts sq }   # 1 4 9

掌握迭代器意味着告别显式索引循环,代码更简洁、更少出错。理解"块 + 方法"的协作方式是 Ruby 进阶的分水岭。

Ruby 文件的输入与输出

Ruby 文件的输入与输出

Ruby 通过 File 类与 IO 类提供完整的文件读写能力。推荐使用块形式的 open,文件会自动关闭,避免资源泄漏。

写入文件

# w: 覆盖写入
File.open("data.txt", "w") do |f|
  f.puts "第一行"
  f.puts "第二行"
end

# a: 追加
File.open("data.txt", "a") { |f| f.puts "追加行" }

读取文件

# 全部读入
content = File.read("data.txt")

# 逐行读取
File.foreach("data.txt") { |line| puts line }

# 逐行读取(带行号)
File.open("data.txt") do |f|
  f.each_with_index { |line, i| puts "#{i+1}: #{line}" }
end

二进制与编码

# 二进制模式
File.open("img.png", "rb") { |f| data = f.read }

# 指定编码
File.open("data.txt", "r:UTF-8") { |f| puts f.read }

IO 标准流

$stdin.readline      # 读取标准输入一行
$stdout.puts "输出"
$stderr.puts "错误信息"

# 管道
IO.popen("ls -la") { |io| puts io.read }

路径与权限

File.exist?("data.txt")    # 是否存在
File.file?("data.txt")     # 是否文件
File.directory?("dir")     # 是否目录
File.size("data.txt")      # 字节数
File.writable?("data.txt") # 是否可写
File.mtime("data.txt")     # 修改时间

错误处理

begin
  File.read("不存在.txt")
rescue Errno::ENOENT
  puts "文件不存在"
rescue Errno::EACCES
  puts "无权限"
end

文件操作务必注意:优先使用块形式确保关闭文件;根据业务选择 UTF-8 编码;处理可能抛出的异常(ENOENT 等)。

Ruby File 类和方法

Ruby File 类和方法

File 类封装了文件系统操作,包括创建、删除、重命名、查询属性等。本节系统介绍 File 类的常用方法。

创建与删除

File.new("a.txt", "w")        # 创建文件(需关闭)
File.write("a.txt", "内容")    # 一步写入

File.delete("a.txt")          # 删除文件
File.unlink("a.txt")          # 同上
File.rename("a.txt", "b.txt") # 重命名

目录操作

Dir.mkdir("mydir")            # 创建目录
Dir.rmdir("mydir")            # 删除空目录
Dir.exist?("mydir")           # 是否存在
Dir.glob("*.rb")              # 通配符匹配
Dir["*.rb"]                   # 同上
Dir.entries(".")              # 目录条目
Dir.mkdir_p("a/b/c")          # 递归创建

文件属性

f = File.new("data.txt")
f.path        # 路径
f.size        # 大小
f.atime       # 访问时间
f.ctime       # 状态变更时间
f.mtime       # 修改时间
f.close       # 关闭

权限与模式

File.readable?("data.txt")  # 可读
File.writable?("data.txt")  # 可写
File.executable?("data.sh") # 可执行
File.chmod(0755, "data.sh") # 修改权限
File.umask                  # 查看默认掩码

常用文件判断

File.exist?("path")     # 存在(文件或目录)
File.file?("path")      # 是普通文件
File.directory?("path") # 是目录
File.symlink?("path")   # 是符号链接
File.zero?("path")      # 是否空文件

逐字节/逐行读取

File.open("big.log") do |f|
  f.each_line { |line| process(line) }
end

处理大文件时优先逐行读取而非 File.read 整体加载;跨平台路径建议使用 File.join 拼接。

Ruby Dir 类和方法

Ruby Dir 类和方法

Dir 类用于目录操作:遍历目录、创建删除目录、匹配文件路径等。它与 File 类配合可完成完整的文件系统管理。

遍历目录

Dir.foreach(".") { |entry| puts entry }
Dir.entries(".")         # 返回数组
Dir.children(".")        # 不含 . 和 ..
Dir.each_child(".") { |d| puts d }

glob 模式匹配

Dir.glob("*.rb")                 # 当前目录所有 rb
Dir.glob("**/*.txt")             # 递归所有 txt
Dir.glob("src/**/*.{rb,js}")     # 多扩展名
Dir.glob("lib/**/").each { |d| puts d }  # 目录

# 等价写法
Dir["*.rb"]

创建与删除

Dir.mkdir("new_dir")
Dir.mkdir("a/b", 0755)    # 指定权限
Dir.rmdir("new_dir")      # 只能删空目录

# 递归删除(慎用)
require "fileutils"
FileUtils.rm_rf("dir")

当前目录操作

puts Dir.pwd          # 当前工作目录
Dir.chdir("/tmp")     # 切换目录
Dir.home              # 用户主目录
Dir.home("root")      # 指定用户主目录

目录属性

Dir.exist?("path")    # 是否存在
Dir.empty?("path")    # 是否为空
Dir["path/*"].size    # 条目数

FileUtils 增强

require "fileutils"

FileUtils.cp("a.txt", "b.txt")    # 复制
FileUtils.mv("a.txt", "c.txt")    # 移动
FileUtils.mkdir_p("x/y/z")        # 递归创建
FileUtils.rm_r("dir")             # 递归删除
FileUtils.touch("new.txt")        # 创建空文件

批量处理文件时,Dir.glob + FileUtils 是 Ruby 社区的标准组合,简洁高效。

Ruby 异常

Ruby 异常

Ruby 使用 begin/rescue/ensure 结构处理异常。异常是对象,继承自 Exception 类。良好的异常处理让程序在错误场景下依然健壮。

基本结构

begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "捕获异常: #{e.message}"
ensure
  puts "无论成败都会执行"
end

多类型捕获

begin
  File.read("missing.txt")
rescue Errno::ENOENT => e
  puts "文件不存在"
rescue Errno::EACCES
  puts "权限不足"
rescue StandardError => e
  puts "其他错误: #{e.class}: #{e.message}"
end

抛出异常

def divide(a, b)
  raise "除数不能为 0" if b.zero?
  a / b
end

# 指定类型
raise ArgumentError, "参数不合法"
raise ArgumentError.new("参数不合法")

# 带 cause
begin
  divide(1, 0)
rescue => e
  raise RuntimeError, "计算失败", e.backtrace
end

自定义异常类

class MyAppError < StandardError; end
class ValidationError < MyAppError; end

raise ValidationError, "邮箱格式错误"

retry 与 else

attempts = 0
begin
  attempts += 1
  risky_call
rescue
  retry if attempts < 3   # 重试最多3次
ensure
  puts "执行完毕"
end

异常层级要点

StandardError 是 rescue 的默认捕获范围;只捕获 Exception 会吞掉 SystemExit、Interrupt 等系统级异常,通常应避免。Ruby 3 还引入了 raise ... cause 链与 Exception#cause 查看异常来源。

Ruby 面向对象

Ruby 面向对象

Ruby 是"纯粹"的面向对象语言:一切皆为对象,包括整数、布尔值、nil。本节系统梳理 Ruby 的 OOP 特性与独到之处。

一切皆对象

1.class            # Integer
true.class         # TrueClass
nil.class          # NilClass
"a".class          # String

# 甚至类本身也是对象
Integer.class      # Class
Integer.superclass # Numeric

封装

class Account
  attr_reader :balance

  def initialize(balance = 0)
    @balance = balance
  end

  def deposit(n)
    @balance += n if n > 0
  end

  private

  def validate
    @balance >= 0
  end
end

继承与多态

class Animal
  def speak; "..." ; end
end

class Dog < Animal
  def speak; "汪汪"; end
end

class Cat < Animal
  def speak; "喵喵"; end
end

[Dog.new, Cat.new].each { |a| puts a.speak }  # 多态

单例方法

str = "hello"
def str.shout
  upcase + "!"
end
str.shout   # "HELLO!"

# 更常见:定义类方法
class Foo
  def self.bar; end   # 类方法 = Class 对象的单例方法
end

方法查找链

obj.singleton_class     # 单例类
obj.class               # 类
obj.class.ancestors     # 继承链(含模块)

Open Class 与 Monkey Patch

class String
  def shout
    upcase + "!"
  end
end
"hi".shout   # "HI!"

# 安全做法:带命名空间的补丁
module StringExt
  refine String do
    def shout; upcase + "!"; end
  end
end

Ruby OOP 灵活度极高,但也需要自律:合理使用访问控制、优先组合而非继承、补丁使用 Refinements 限定作用域。

Ruby 正则表达式

Ruby 正则表达式

Ruby 内置正则表达式,语法类似 Perl。正则用于文本匹配、提取、替换,是处理字符串的利器。

基本语法

# 字面量写法
re = /hello/
re = %r{hello}

# 匹配
"hello world" =~ /hello/    # 返回位置 0
/hello/.match("hello world")# MatchData 对象
"hello" === /hello/         # true

常用模式

/a.c/      # . 匹配任意字符
/ab*/      # * 前一个字符0次或多次
/ab+/      # + 一次或多次
/a?b/      # ? 0次或1次
/[abc]/    # 字符类
/[a-z]/    # 范围
/[^0-9]/   # 取反
/\d/       # 数字 = [0-9]
/\w/       # 单词字符
/\s/       # 空白
/^start/   # 行首
/end$/     # 行尾

捕获与替换

m = /(\d{4})-(\d{2})-(\d{2})/.match("2026-08-17")
m[1]    # "2026"
m[2]    # "08"
m[3]    # "17"

"Hello Ruby".gsub(/(Ruby)/, '[]')   # 分组引用
"abc123".sub(/\d+/, "X")              # 替换第一处

正则选项

/ruby/i        # 忽略大小写
/ruby/m        # 多行模式
/ruby/x        # 忽略空白(可加注释)
/ruby/u        # UTF-8

命名捕获

m = /(?<year>\d{4})-(?<month>\d{2})/.match("2026-08")
m[:year]    # "2026"
m["month"]  # "08"

scan 与 split

"a1b2c3".scan(/\d/)          # ["1","2","3"]
"a,b;c".split(/[,;]/)         # ["a","b","c"]
"Hello World".scan(/\w+/)     # ["Hello","World"]

复杂正则建议用 /x 模式加注释分段,并配合在线工具测试。正则虽强大,过度使用反而降低可读性,简单场景用字符串方法即可。

Ruby 数据库访问 - DBI 教程

Ruby 数据库访问 - DBI 教程

DBI(Database Interface)是 Ruby 早期统一的数据库访问接口,类似 Java 的 JDBC。现代开发更推荐使用 mysql2、pg、sqlite3 等驱动或 ActiveRecord ORM,但理解 DBI 有助于掌握数据库编程思路。

DBI 架构

DBI 分为两层:上层 DBI 提供统一 API,下层 DBD(数据库驱动)对接具体数据库(MySQL、PostgreSQL、SQLite 等)。

安装

gem install dbi
gem install dbd-mysql     # MySQL 驱动
gem install dbd-pg        # PostgreSQL 驱动

连接数据库

require "dbi"

dbh = DBI.connect("DBI:Mysql:database=test;host=localhost",
                  "username", "password")

执行查询

# 查询
sth = dbh.prepare("SELECT id, name FROM users WHERE age > ?")
sth.execute(18)
while row = sth.fetch
  puts "#{row[0]} #{row[1]}"
end
sth.finish

# 简便方式
dbh.select_all("SELECT * FROM users").each do |row|
  puts row["name"]
end

插入与更新

dbh.do("INSERT INTO users(name, age) VALUES(?, ?)", "张三", 25)
dbh.do("UPDATE users SET age = ? WHERE id = ?", 26, 1)

事务处理

dbh.transaction do
  dbh.do("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
  dbh.do("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
end

现代替代方案

# mysql2 驱动示例
require "mysql2"
client = Mysql2::Client.new(host: "localhost",
                            username: "root",
                            database: "test")
results = client.query("SELECT * FROM users")
results.each { |row| puts row["name"] }

使用 ? 占位符预编译语句可有效防止 SQL 注入,任何数据库访问都应遵循这一原则。

Ruby CGI 编程

Ruby CGI 编程

CGI(Common Gateway Interface)是最早的 Web 动态页面技术:Web 服务器把请求转给 CGI 程序,程序输出 HTML 返回客户端。Ruby 内置 CGI 库,适合学习 Web 工作原理。

最小 CGI 程序

#!/usr/bin/env ruby
require "cgi"

cgi = CGI.new
puts cgi.header
puts "<html><body><h1>你好, Ruby CGI</h1></body></html>"

读取参数

require "cgi"
cgi = CGI.new

# GET/POST 参数
name = cgi["name"]
age  = cgi["age"]

# 所有参数
cgi.params.each { |k, v| puts "#{k}=#{v}" }

处理表单

require "cgi"
cgi = CGI.new

if cgi.request_method == "POST"
  user = cgi["user"]
  puts cgi.header
  puts "<p>提交的用户:#{CGI.escapeHTML(user)}</p>"
else
  puts cgi.header
  puts "<form method='post'>"
  puts "<input name='user'>"
  puts "<button>提交</button></form>"
end

Cookie 与 Session

cgi = CGI.new
cookie = CGI::Cookie.new("name" => "user_id",
                         "value" => "12345",
                         "expires" => Time.now + 3600)
puts cgi.header("cookie" => cookie)

输出 HTML 与转义

CGI.escapeHTML("<script>")   # 防止 XSS
CGI.escape("a b&c")            # URL 编码
CGI.unescape("a%20b")

服务器配置

将 CGI 程序放到服务器 cgi-bin 目录并赋予执行权限,Apache/Nginx 配置 CGI 处理器后即可通过 URL 访问。

现代开发中 CGI 已被 Rack(以及 Rails/Sinatra 等框架)取代,但 CGI 知识仍是理解 HTTP 请求-响应模型的最佳起点。

Ruby CGI方法

Ruby CGI 方法

CGI 类提供了一系列方法用于处理 HTTP 请求:参数解析、Header 生成、Cookie 管理等。本节梳理常用方法。

初始化与请求信息

cgi = CGI.new

cgi.request_method   # "GET" / "POST"
cgi.query_string     # 查询字符串
cgi.path_info        # 路径信息
cgi.remote_addr      # 客户端 IP
cgi.user_agent       # 浏览器 UA
cgi.http_accept_language

参数访问

cgi["name"]             # 单个参数(字符串)
cgi.params              # 全部参数 Hash
cgi.params["name"]      # 参数数组
cgi.has_key?("name")    # 是否存在
cgi.keys                # 所有参数名
cgi.master_key         # 密码保护的多文件

Header 方法

cgi.header              # 默认 text/html
cgi.header("type" => "text/plain")
cgi.header("status" => "404 Not Found")
cgi.header("location" => "http://example.com/")
cgi.header("cookie" => cookie)

输出辅助

cgi.out { "HTML 内容" }        # 输出 header + 内容
cgi.print("直接输出")
CGI.escapeHTML(str)            # HTML 转义
CGI.escape(str) / CGI.unescape(str)

文件上传

cgi = CGI.new
if cgi.params["file"]&.first.is_a?(StringIO)
  file = cgi.params["file"].first
  File.open("upload.txt", "w") { |f| f.write(file.read) }
end

multipart 与编码

cgi.accept_charset     # 客户端字符集
cgi.charset            # 当前字符集
CGI.new(charset: "UTF-8")

CGI 方法虽多,核心就是三件事:取参数、读环境、输出响应。理解这些方法后,过渡到 Rack/Sinatra 会非常自然。

Ruby CGI Cookie

Ruby CGI Cookie

Cookie 是服务器保存在客户端的小段数据,用于维持会话状态(如登录态、购物车)。CGI 库提供 Cookie 类的完整封装。

创建 Cookie

require "cgi"

cookie = CGI::Cookie.new(
  "name"    => "user_id",
  "value"   => "12345",
  "expires" => Time.now + 3600,   # 过期时间
  "path"    => "/",               # 作用路径
  "domain"  => "example.com",
  "secure"  => true,              # 仅 HTTPS
  "httponly" => true              # 禁止 JS 读取
)

发送 Cookie

cgi = CGI.new
cookie = CGI::Cookie.new("name" => "login", "value" => "ok")
puts cgi.header("cookie" => cookie)
puts "<html>...</html>"

读取 Cookie

cgi = CGI.new
user_id = cgi.cookies["user_id"]&.first
puts "用户ID: #{user_id}"

多个 Cookie

c1 = CGI::Cookie.new("name" => "a", "value" => "1")
c2 = CGI::Cookie.new("name" => "b", "value" => "2")
puts cgi.header("cookie" => [c1, c2])

删除 Cookie

cookie = CGI::Cookie.new(
  "name" => "user_id",
  "value" => "",
  "expires" => Time.at(0)   # 过去时间即删除
)

Cookie 属性说明

属性说明
expires过期时间,不设则会话结束失效
path生效路径,默认当前路径
domain生效域名
secure仅 HTTPS 传输
httponly禁止脚本访问(防 XSS 窃取)
max_age存活秒数(现代替代 expires)

安全注意事项

  • 敏感信息不要明文存 Cookie,建议只存会话 ID
  • 启用 HttpOnly 与 Secure 属性
  • 会话 ID 应随机生成并设置过期时间
  • 校验 Cookie 时防止伪造(加签名或服务端校验)

Cookie 与 Session 配合是现代 Web 会话管理的基础,理解 Cookie 机制对排查登录问题非常有帮助。

Ruby CGI Session

Ruby CGI Session

Session(会话)是服务端保存的用户状态数据,通过 Cookie 中的 session_id 关联。相比直接存 Cookie,Session 更安全,可存任意大小数据。

创建 Session

require "cgi"
require "cgi/session"

cgi = CGI.new
session = CGI::Session.new(cgi,
  "session_key"  => "session_id",
  "session_id"   => nil,       # 自动生成
  "new_session"  => true,
  "database_manager" => CGI::Session::FileStore,  # 存储方式
  "tmpdir"       => "/tmp/cgi_sessions"           # 文件目录
)

写入与读取

session["user"] = "张三"
session["login_time"] = Time.now.to_s

puts session["user"]        # 读取
session.key?("user")        # 判断存在

输出 session_id

# Session 首次创建后需要把 session_id 写回浏览器
puts cgi.header("cookie" => session.update)

删除 Session

session.delete       # 删除数据
session.close        # 关闭(保持数据)
session.update       # 刷新输出

完整登录示例

require "cgi"
require "cgi/session"

cgi = CGI.new
session = CGI::Session.new(cgi,
  "database_manager" => CGI::Session::FileStore)

if cgi.request_method == "POST" && cgi["pwd"] == "secret"
  session["login"] = "true"
  puts cgi.header("cookie" => session.update)
  puts "<p>登录成功</p>"
elsif session["login"] == "true"
  puts cgi.header
  puts "<p>欢迎回来</p>"
else
  puts cgi.header
  puts "<form method='post'><input type='password' name='pwd'><button>登录</button></form>"
end

session.close

存储方式选择

  • FileStore:文件存储,简单可靠,适合小流量
  • MemoryStore:内存存储,快但进程重启丢失
  • PStore:PStore 文件存储,支持对象

生产环境建议使用数据库或 Redis 存储 Session,并设置合理的过期时间,定期清理过期会话文件。

Ruby 发送邮件 - SMTP

Ruby 发送邮件 - SMTP

Ruby 的 net/smtp 库提供 SMTP 协议支持,可以发送纯文本与 HTML 邮件;配合 mail 等 gem 使用更便捷。本节演示核心用法。

简单发送

require "net/smtp"

message = <<~MESSAGE_END
From: 发件人 <from@example.com>
To: 收件人 <to@example.com>
Subject: 测试邮件

你好,这是一封来自 Ruby 的测试邮件!
MESSAGE_END

Net::SMTP.start("smtp.example.com", 25,
                "localhost", "username", "password", :plain) do |smtp|
  smtp.send_message(message, "from@example.com", ["to@example.com"])
end

发送 HTML 邮件

html = <<~HTML
<html><body><h2>欢迎!</h2><p>注册成功</p></body></html>
HTML

message = <<~MSG
From: noreply@example.com
To: user@example.com
Subject: 注册确认
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8

#{html}
MSG

带附件(使用 mail gem 更简单)

require "mail"

mail = Mail.new do
  from    "noreply@example.com"
  to      "user@example.com"
  subject "月报"
  body    "请查收附件"

  add_file "report.pdf"
end

mail.delivery_method :smtp, address: "smtp.example.com",
                             port: 587,
                             user_name: "user",
                             password: "pass",
                             authentication: :plain,
                             enable_starttls_auto: true
mail.deliver

TLS/SSL 支持

Net::SMTP.start("smtp.example.com", 465,
  "localhost", "user", "pass", :login,
  ssl_context: OpenSSL::SSL::SSLContext.new) do |smtp|
  smtp.send_message(msg, "from@example.com", ["to@example.com"])
end

常见邮箱服务器参数

  • QQ 邮箱:SMTP 587/TLS,授权码代替密码
  • 163 邮箱:SMTP 465/SSL 或 25
  • Gmail:SMTP 587,需应用专用密码

生产环境注意:不要硬编码密码,使用环境变量;发送失败时捕获 Net::SMTPError 并重试。

Ruby Socket 编程

Ruby Socket 编程

Socket 是网络编程的基础抽象。Ruby 的 socket 库提供 TCP/UDP 客户端与服务器开发能力,是理解网络应用原理的重要一课。

TCP 服务器

require "socket"

server = TCPServer.new("0.0.0.0", 2000)
puts "服务器启动,监听 2000 端口"

loop do
  client = server.accept      # 等待连接
  Thread.new(client) do |c|
    c.puts "连接成功"
    while line = c.gets
      c.puts "回显: #{line}"
    end
    c.close
  end
end

TCP 客户端

require "socket"

client = TCPSocket.new("localhost", 2000)
puts client.gets            # 连接成功

client.puts "你好,服务器"
puts client.gets            # 回显: 你好,服务器

client.close

UDP 通信

require "socket"

# 服务器
udp = UDPSocket.new
udp.bind("0.0.0.0", 2001)
data, addr = udp.recvfrom(1024)
puts "收到: #{data} 来自 #{addr[3]}"

# 客户端
udp = UDPSocket.new
udp.send("hello", 0, "localhost", 2001)

HTTP 请求(基于 Socket)

require "socket"

sock = TCPSocket.new("www.example.com", 80)
sock.write("GET / HTTP/1.1
Host: www.example.com
Connection: close

")
puts sock.read
sock.close

获取主机信息

require "socket"

Socket.gethostname            # 主机名
Socket.getaddrinfo("example.com", 80)  # DNS 解析
IPSocket.getaddress("example.com")     # 获取 IP

异常处理

begin
  client = TCPSocket.new("example.com", 80, 5)  # 5秒超时
rescue Errno::ECONNREFUSED
  puts "连接被拒绝"
rescue Errno::ETIMEDOUT
  puts "连接超时"
end

Socket 编程是理解 HTTP、WebSocket、数据库驱动等一切网络通信的基础,掌握 TCP 流程后进阶异步框架会事半功倍。

Ruby XML, XSLT 和 XPath 教程

Ruby XML、XSLT 和 XPath 教程

XML 是结构化数据的经典格式。Ruby 提供 REXML 解析 XML,XSLT 用于样式转换,XPath 用于节点查询。本节介绍核心用法(JSON 已逐渐取代 XML,但配置文件、旧系统集成仍常见)。

REXML 解析

require "rexml/document"
include REXML

xml = <<~XML
<bookstore>
  <book id="1">
    <title>Ruby 入门</title>
    <price>59.0</price>
  </book>
  <book id="2">
    <title>Ruby 进阶</title>
    <price>79.0</price>
  </book>
</bookstore>
XML

doc = Document.new(xml)

XPath 查询

include REXML

doc = Document.new(xml)

# 查询所有书名
XPath.each(doc, "//title") { |el| puts el.text }

# 按条件查询
book = XPath.first(doc, "//book[@id='2']")
puts book.elements["title"].text   # Ruby 进阶

# 属性
XPath.first(doc, "//book/@id")

遍历与修改

doc.root.each_element do |book|
  puts "#{book.attributes['id']}: #{book.elements['title'].text}"
end

# 修改
book.elements["price"].text = "99.0"
doc.write($stdout)

创建 XML

include REXML

doc = Document.new
root = doc.add_element("users")
user = root.add_element("user")
user.add_attribute("id", "1")
user.add_element("name").text = "张三"

doc.write($stdout)

XSLT 转换(需安装 nokogiri)

require "nokogiri"

xml = Nokogiri::XML(File.read("data.xml"))
xslt = Nokogiri::XSLT(File.read("style.xsl"))
html = xslt.transform(xml)
puts html

现代建议

新项目优先使用 JSON/YAML;处理 XML 时 Nokogiri 性能与功能远强于 REXML,解析不可信 XML 注意 XXE 防护(禁用外部实体)。

Ruby Web Service

Ruby Web Service

Web Service 是跨语言、跨平台的远程调用标准,主要有 SOAP 与 REST 两种风格。Ruby 通过 soap4r(SOAP)与标准 HTTP 库(REST)提供服务与消费能力。

RESTful API 消费

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

uri = URI("https://api.example.com/users")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

# GET
resp = http.get(uri)
users = JSON.parse(resp.body)

# POST
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = { name: "张三" }.to_json
resp = http.request(req)

使用 Faraday / HTTParty

require "httparty"

resp = HTTParty.get("https://api.github.com/users/octocat")
puts resp["login"]

HTTParty.post("https://api.example.com/users",
  body: { name: "李四" }.to_json,
  headers: { "Content-Type" => "application/json" })

SOAP 服务(soap4r)

require "soap/wsdlDriver"

soap = SOAP::WSDLDriverFactory.new("http://example.com/service?wsdl").create_rpc_driver
result = soap.getUser("1001")
puts result

创建简单 REST 服务

require "sinatra"

get "/api/users" do
  content_type :json
  [{ id: 1, name: "张三" }].to_json
end

post "/api/users" do
  params.to_json
end

GraphQL 与 gRPC(现代方向)

# graphql-ruby 提供 GraphQL 服务
# grpc gem 提供 gRPC 支持
require "grpc"

选择建议

  • 公开 API:优先 REST/GraphQL(JSON)
  • 遗留系统对接:SOAP(WSDL)
  • 微服务内部:gRPC(高性能)

无论哪种风格,核心都是"序列化 + 传输 + 反序列化"。理解 HTTP 状态码、鉴权方式与错误处理是调用任何 Web Service 的基础。

Ruby 多线程

Ruby 多线程

Ruby 通过 Thread 类实现多线程。MRI(标准 Ruby)受 GVL(全局虚拟机锁)限制,CPU 密集任务无法真正并行,但 I/O 密集任务(网络请求、文件读写)可以显著提速。

创建线程

threads = []
5.times do |i|
  threads << Thread.new(i) do |n|
    sleep(rand(0.1..0.3))
    puts "线程 #{n} 完成"
  end
end
threads.each(&:join)   # 等待所有线程结束
puts "全部完成"

线程变量与参数

t = Thread.new do
  Thread.current[:name] = "worker"
  sleep 0.1
end
t.join
puts t[:name]    # 线程局部变量

共享数据与锁

counter = 0
mutex = Mutex.new

threads = 10.times.map do
  Thread.new do
    1000.times do
      mutex.synchronize { counter += 1 }
    end
  end
end
threads.each(&:join)
puts counter   # 10000(不加锁可能小于此值)

线程状态与控制

t = Thread.new { sleep 5 }
t.alive?    # true
t.status    # "sleep"
t.wakeup
t.exit      # 强制终止(慎用)
t.kill

异常处理

t = Thread.new { raise "线程内错误" }
begin
  t.join       # 异常会在 join 时抛出
rescue => e
  puts "捕获: #{e.message}"
end

队列实现生产者消费者

require "thread"

queue = Queue.new

producer = Thread.new { 5.times { |i| queue << i; sleep 0.1 } }
consumer = Thread.new { 5.times { puts "消费: #{queue.pop}" } }
[producer, consumer].each(&:join)

JRuby / TruffleRuby 并行

JRuby 与 TruffleRuby 没有 GVL,可以真正利用多核。对 CPU 密集场景,或使用 Ractors(Ruby 3 的并行抽象):

ractor = Ractor.new { 1 + 1 }
puts ractor.take   # 2

多线程核心原则:最小化共享状态、用 Mutex/Queue 保护共享资源、避免死锁。I/O 密集用线程,CPU 密集用 Ractor 或进程。

Ruby JSON

Ruby JSON

JSON 是现代 Web 数据交换的事实标准。Ruby 标准库 json 提供完整的解析与生成能力,还支持 JSON Schema 风格的扩展。

解析 JSON

require "json"

str = '{"name":"Ruby","age":30,"tags":["web","script"]}'
data = JSON.parse(str)

data["name"]    # "Ruby"
data["age"]     # 30
data["tags"]    # ["web","script"]

生成 JSON

require "json"

h = { name: "Ruby", age: 30, tags: %w[web script] }
JSON.generate(h)   # {"name":"Ruby",...}
h.to_json          # 等价

# 美化输出
JSON.pretty_generate(h)

符号键处理

data = JSON.parse(str, symbolize_names: true)
data[:name]    # "Ruby"

# 嵌套对象
user = JSON.parse('{"user":{"name":"张三"}}')
user["user"]["name"]

对象序列化

require "json"

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name, @age = name, age
  end

  def to_json(*)
    { name: @name, age: @age }.to_json
  end

  def self.from_json(json)
    d = JSON.parse(json)
    new(d["name"], d["age"])
  end
end

p = Person.new("张三", 25)
json = p.to_json
p2 = Person.from_json(json)

文件读写

require "json"

# 写文件
File.write("data.json", JSON.pretty_generate(data))

# 读文件
data = JSON.parse(File.read("data.json"))

错误处理

begin
  JSON.parse("invalid json")
rescue JSON::ParserError => e
  puts "JSON 解析失败: #{e.message}"
end

JSON 使用注意:嵌套层级深的大文档解析较慢;不可信 JSON 可能包含超大数组,可用 JSON.parse(str, max_nesting: 10) 限制深度防 DoS。

Ruby RubyGems

Ruby RubyGems

RubyGems 是 Ruby 的包管理器,提供 gem 的搜索、安装、发布与依赖管理。Bundler 在此基础上实现项目的依赖锁定与可复现环境。

gem 常用命令

gem list                    # 已安装的 gem
gem search rails            # 搜索
gem install rails           # 安装
gem uninstall rails         # 卸载
gem update --system         # 升级 gem 本身
gem env                     # 查看环境信息
gem sources -l              # 查看源

国内源配置

gem sources --add https://gems.ruby-china.com/
gem sources --remove https://rubygems.org/
gem sources -l

Bundler 使用

# Gemfile
source "https://gems.ruby-china.com"

gem "rails", "~> 7.0"
gem "puma"
gem "sqlite3", group: :development

# 安装与锁定
bundle install
bundle exec rails server   # 在锁定环境运行

创建自己的 gem

gem build mygem.gemspec
gem install mygem-0.1.0.gem

# 发布
gem push mygem-0.1.0.gem

gemspec 基础结构:

Gem::Specification.new do |s|
  s.name        = "mygem"
  s.version     = "0.1.0"
  s.summary     = "示例 gem"
  s.authors     = ["Your Name"]
  s.files       = Dir["lib/**/*.rb"]
  s.require_paths = ["lib"]
end

gem 结构约定

mygem/
├── lib/mygem.rb      # 入口
├── lib/mygem/        # 模块文件
├── spec/             # 测试
├── Gemfile
└── mygem.gemspec

常用高质量 gem

  • Web:rails、sinatra、rack
  • HTTP:httparty、faraday
  • 数据库:mysql2、pg、sqlite3、activerecord
  • 测试:rspec、minitest
  • 其他:nokogiri(HTML/XML)、sidekiq(后台任务)

安装 gem 前建议查看其维护状态与依赖;使用 Bundler 管理版本,避免环境漂移问题。

第 1 / 42 节