基础
变量与类型
Ruby 是动态类型的——变量不需要类型声明。使用 .class 检查类型,使用 is_a? 进行类型检查。一切都是对象,包括数字和布尔值。
name = "Alice" # string
age = 30 # integer
pi = 3.14 # float
is_dev = true # boolean
nums = [1, 2, 3] # array
puts name.class # String
puts age.is_a?(Integer) # true符号
符号(:name)是不可变的、驻留的字符串——内存中只存在一个副本。将它们用于哈希键、方法名和标识比内容更重要的标识符。对于重复使用,比字符串更节省内存。
status = :active
puts status.class # Symbol
puts status.to_s # "active"
# Symbols are immutable, reusable strings
hash = { name: "Alice", status: :active }
puts hash[:status] # activeNil 与真值
在 Ruby 中,只有 nil 和 false 是假值——其他所有值(包括 0、'' 和 [])都是真值。这与许多 0 为假值的语言不同。使用 || 提供默认值,使用 nil? 专门检查 nil。
x = nil
puts x.nil? # true
puts x || "default" # default
# Only nil and false are falsy; 0 and "" are truthy!
puts 0 ? "truthy" : "falsy" # truthy
puts "" ? "truthy" : "falsy" # truthy
puts nil ? "truthy" : "falsy" # falsy类型转换
to_i/to_s/to_f 是宽松转换(失败时返回 0)。Integer()/Float() 是严格的(引发 ArgumentError)。需要验证时使用严格转换,想要优雅降级时使用宽松转换。
puts "42".to_i # 42
puts 42.to_s # "42"
puts "3.14".to_f # 3.14
puts 3.14.to_i # 3
puts "abc".to_i # 0 (no error)
puts Integer("42") # 42 (raises if invalid)字符串插值
双引号字符串支持 #{expr} 插值——内部可以是任何 Ruby 表达式。单引号字符串是字面量(无插值或转义,除了 \\ 和 \')。需要插值或转义序列时使用双引号。
name = "Alice"
age = 30
puts "Name: #{name}, Age: #{age}"
puts "5 + 3 = #{5 + 3}"
puts "Upper: #{name.upcase}"
# Single quotes don't interpolate
puts 'No #{name} here' # No #{name} here字符串
常用字符串方法
Ruby 字符串有丰富的方法。大多数返回新字符串(Ruby 中字符串是可变的)。使用 ! 变体(upcase!、gsub!)进行原地修改——它们修改接收者,如果无更改则返回 nil。
s = "Hello, World"
puts s.length # 12
puts s.upcase # HELLO, WORLD
puts s.downcase # hello, world
puts s.reverse # dlroW ,olleH
puts s.split(", ") # ["Hello", "World"]
puts s.gsub("o", "0") # Hell0, W0rld字符串修改(Bang 方法)
以 ! 结尾的方法原地修改对象,通常更高效。如果未进行更改,它们可能返回 nil。当你想避免创建副本时使用,但要注意副作用。
s = "hello"
s.upcase! # s is now "HELLO"
s.gsub!(/L/, "1") # s is now "HE11O"
puts s # HE11O
# Bang methods modify in-place; use with caution
arr = [3, 1, 2]
arr.sort! # arr is now [1, 2, 3]Heredoc 与多行
Heredoc(<<TEXT ... TEXT)创建多行字符串。<<~(波浪号 heredoc)去除公共前导空白以保持代码整洁。适用于 SQL 查询、HTML 模板或长消息。
text = <<~HEREDOC
Hello,
World!
Indentation is stripped.
HEREDOC
puts text
# <<~ strips leading whitespace (Ruby 2.3+)
# <<HEREDOC preserves indentation格式化(sprintf)
使用 % 运算符或 format/sprintf 进行 C 风格字符串格式化。%-10s 在 10 个字符中左对齐,%08.2f 零填充到 8 个字符并保留 2 位小数。适用于表格输出和固定宽度格式化。
puts sprintf("%s is %d", "Alice", 30)
puts "%-10s|%5d" % ["Name", 42]
puts format("%.2f", 3.14159) # 3.14
puts "%08.2f" % 3.14 # 00003.14
# %s string, %d integer, %f float字符串连接
+ 创建新字符串,<< 原地追加(构建字符串更高效)。* 重复字符串。join 用分隔符组合数组元素。性能上优先使用 << 或 join 而非重复的 +。
s1 = "Hello" + ", " + "World"
s2 = ["a", "b", "c"].join("-") # a-b-c
s3 = "Hello"
s3 << " " << "World" # in-place append
s4 = "x" * 3 # xxx (repetition)
puts s1, s2, s3, s4数据结构
数组
数组是有序的、从零索引的,可以保存混合类型。<< 和 push 添加到末尾。使用 include? 检查成员,使用 sum 求和。数组是可变的——使用 freeze 使其不可变。
nums = [1, 2, 3, 4, 5]
nums.push(6) # [1,2,3,4,5,6]
nums << 7 # same as push
nums[0] = 0
puts nums.first # 0
puts nums.length # 7
puts nums.sum # 28
puts nums.include?(3) # true哈希
哈希是键值字典。符号键(name:)是惯用的且高效。使用 key? 检查存在,使用 fetch 安全访问(缺失时引发异常),使用 transform_values 批量更新。
user = { name: "Alice", age: 30 }
user[:email] = "[email protected]"
puts user[:name] # Alice
puts user.key?(:name) # true
user.each { |k, v| puts "#{k}: #{v}" }
puts user.values # ["Alice", 30, "[email protected]"]
puts user.transform_values(&:to_s)范围
'..' 包含结尾,'...' 不包含。范围是惰性的,对于大序列节省内存。适用于迭代、切片和生成序列。可用于任何 Comparable 类型。
(1..5).each { |n| puts n } # 1 2 3 4 5
puts (1...5).to_a # [1, 2, 3, 4]
puts (1..10).select(&:even?) # [2, 4, 6, 8, 10]
puts ('a'..'e').include?('c') # true
puts (1..5).map { |n| n ** 2 } # [1, 4, 9, 16, 25]Enumerable 方法
Enumerable 是 Ruby 最强大的混入——map、select、reject、reduce、find、group_by、sort_by 等。使用 &:method 作为 { |x| x.method } 的简写。这些实现了富有表现力的数据转换管道。
nums = [1, 2, 3, 4, 5]
puts nums.map { |n| n * 2 }.inspect # [2,4,6,8,10]
puts nums.select(&:even?).inspect # [2, 4]
puts nums.reject(&:odd?).inspect # [2, 4]
puts nums.reduce(0) { |sum, n| sum + n } # 15
puts nums.find { |n| n > 3 } # 4
puts nums.group_by(&:even?).inspect # {false=>[1,3,5], true=>[2,4]}集合
Set(来自 'set' 库)存储唯一元素,O(1) 查找。用于去重和集合操作(并集、交集、差集)。需要数组时用 to_a 转换。需要 require 'set'。
require 'set'
a = Set.new([1, 2, 3])
b = Set.new([3, 4, 5])
puts a.union(b).to_a.inspect # [1,2,3,4,5]
puts a.intersection(b).to_a.inspect # [3]
puts a.subtract([1]).to_a.inspect # [2, 3]
puts a.subset?(Set.new([1,2,3,4])) # true
a.add(6)控制流
If / Elsif / Unless
if/elsif/else 是标准分支。unless 是 if 的相反(条件为假时执行)。修饰符形式(statement if condition)是单行守卫的惯用写法——提高简单情况的可读性。
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "C"
end
# Modifier form
puts "Pass" if score >= 60
puts "Fail" unless score >= 60Case(When)
case/when 使用 === 进行匹配,支持范围、类和正则表达式。多个值用逗号分隔。then 允许单行体。没有目标时,case 充当更清晰的 if/elsif 链。
grade = "B"
case grade
when "A" then puts "Excellent"
when "B", "C" then puts "Good"
when "D".."F" then puts "Poor"
else puts "Unknown"
end
# Case without value = multi-condition if
case
when score > 90 then puts "Top"
when score > 60 then puts "Pass"
endWhile / Until / Loop
while 在为真时运行,until 运行直到为真(即为假时)。loop 是无限的——使用 break 退出。集合优先使用迭代器(each、map)而非 while——它们更惯用且不易出错。
count = 0
while count < 3
puts count
count += 1
end
# until = opposite of while
n = 3
until n == 0
puts n
n -= 1
end
# Infinite loop with break
loop do
puts "forever"
break if rand > 0.8
end迭代器(Each/Times/Upto)
Ruby 的迭代器比 for 循环更惯用。times 用于计数,upto/downto 用于范围,each_with_index 用于索引+值。step 控制增量。这些是 Ruby 迭代的骨干。
3.times { |i| puts i } # 0 1 2
1.upto(3) { |n| puts n } # 1 2 3
3.downto(1) { |n| puts n } # 3 2 1
[1,2,3].each_with_index do |n, i|
puts "#{i}: #{n}"
end
(1..3).step(1) { |n| puts n } # 1 2 3Break / Next / Redo
next 跳到下一次迭代(类似 continue),break 提前退出循环。break 可以从块返回值。redo 重新开始当前迭代而不重新检查条件——很少使用。
[1, 2, 3, 4, 5].each do |n|
next if n.even? # skip even
break if n > 4 # stop at 5
puts n # prints 1, 3
end
# break value returns from the block
result = [1,2,3].each { |n| break n * 10 if n == 2 }
puts result # 20方法与块
方法定义
方法使用 def/end。关键字参数(key:)提高多参数的可读性。*args 将额外位置参数收集到数组中,**kwargs 将关键字参数收集到哈希中。默认值使用 =。
def greet(name, greeting: "Hello")
"#{greeting}, #{name}!"
end
puts greet("Alice") # Hello, Alice!
puts greet("Bob", greeting: "Hi") # Hi, Bob!
def add(*nums) # splat (variadic)
nums.sum
end
puts add(1, 2, 3, 4) # 10块与 Yield
块是传递给方法的匿名代码块。yield 调用块。块在 Ruby 中无处不在(each、map 等)。使用 yield 使方法灵活——调用者提供行为。
def repeat(n)
n.times { yield }
end
repeat(3) { puts "hi" } # prints hi 3 times
def with_result
yield(5)
end
puts with_result { |x| x * 2 } # 10Proc 与 Lambda
Proc 和 lambda 是存储在变量中的可复用块。关键区别:lambda 检查参数数量(不匹配时引发异常)且仅从自身返回;proc 是宽松的并从封闭方法返回。优先使用 lambda 获得严格行为。
square = proc { |x| x * x }
puts square.call(5) # 25
puts square.(5) # 25 (shortcut)
double = lambda { |x| x * 2 }
puts double.call(5) # 10
# Lambda checks arity; Proc doesn't
# Lambda returns from itself; Proc returns from enclosing method方法对象(&)
&:method 将方法名转换为 Proc。它是简单单方法块的惯用简写:map(&:to_i) 而非 map { |s| s.to_i }。对于简单转换更清晰、更可读。
nums = ["1", "2", "3"]
ints = nums.map(&:to_i) # [1, 2, 3]
puts ints.inspect
# &:to_i is shorthand for { |s| s.to_i }
names = ["alice", "bob"]
up = names.map(&:upcase) # ["ALICE", "BOB"]
puts up.inspect返回值
Ruby 方法隐式返回最后求值的表达式——无需显式 return。使用 return 提前退出。多个值作为数组返回并可解构。这使代码简洁。
def status(ok)
return "error" unless ok
"ok" # implicit return of last expression
end
puts status(true) # ok
puts status(false) # error
# Multiple assignment
def coords
return 1, 2 # returns [1, 2]
end
x, y = coords类与面向对象
类与实例变量
@var = 实例变量(每个对象),@@var = 类变量(共享)。attr_accessor 生成 getter+setter,attr_reader 仅 getter,attr_writer 仅 setter。self.method 定义类方法。
class Person
attr_accessor :name, :age
attr_reader :id
@@count = 0 # class variable
def initialize(name, age)
@name = name # instance variable
@age = age
@@count += 1
end
def self.count; @@count; end
end
p = Person.new("Alice", 30)
puts p.name, Person.count继承与 Super
< 表示继承(仅单继承)。super 调用父类的当前方法版本。使用 super(带括号传递参数,不带传递相同参数)扩展父行为。Ruby 使用单继承 + 混入。
class Animal
def initialize(name); @name = name; end
def speak; "..."; end
end
class Dog < Animal
def speak; "#{@name}: Woof!"; end
end
class Puppy < Dog
def speak; "#{super} (small)"; end
end
puts Puppy.new("Rex").speak # Rex: Woof! (small)模块(混入)
模块分组可复用方法。include 添加实例方法,extend 添加类方法。这是 Ruby 对多重继承的解决方案。Enumerable 是著名的混入——包含它并定义 each 即可获得 map、select 等。
module Walkable
def walk; "#{@name} is walking"; end
end
module Swimmable
def swim; "#{@name} is swimming"; end
end
class Duck
include Walkable # instance methods
extend Swimmable # class methods
def initialize(n); @name = n; end
end
puts Duck.new("Donald").walk # Donald is walking访问控制
public(默认)、private(仅可无显式接收者调用)、protected(可在类层次结构内调用)。内部辅助方法使用 private,同一类实例之间共享的方法使用 protected。
class BankAccount
def initialize(bal); @balance = bal; end
def deposit(amt); @balance += amt; end
def balance; @balance; end
private
def audit; "auditing..."; end
protected
def compare(other); @balance > other.balance; end
end
a = BankAccount.new(100)
puts a.balance # 100
# a.audit # Error: private methodSelf 与类方法
self 指当前对象。在类体内,self 是类——def self.method 定义类方法。类方法在类上调用(Counter.total),实例方法在实例上调用。alias_method 创建方法别名。
class Counter
@@total = 0
def initialize; @@total += 1; end
def self.total; @@total; end
def self.reset!; @@total = 0; end
def instance_method; "I'm an instance"; end
alias_method :count, :instance_method
end
Counter.new; Counter.new
puts Counter.total # 2错误处理
Begin / Rescue / Ensure
begin/rescue 是 Ruby 的 try/catch。针对特定异常类进行救援以进行针对性处理。=> e 捕获异常对象。ensure 总是运行——用于清理(关闭文件、释放锁)。
begin
result = 10 / 0
rescue ZeroDivisionError => e
puts "Caught: #{e.message}"
rescue => e
puts "Other error: #{e.class}"
ensure
puts "Always runs (cleanup)"
end
# ensure runs regardless of success/failureRaise 与自定义异常
raise 抛出异常(不带参数的 raise 重新抛出)。自定义异常继承自 StandardError(或更具体的类)。用 Error 后缀命名。按类救援以处理特定失败模式。
class InvalidAgeError < StandardError; end
def set_age(age)
raise InvalidAgeError, "Age cannot be negative" if age < 0
raise ArgumentError, "Must be Integer" unless age.is_a?(Integer)
@age = age
end
begin
set_age(-5)
rescue InvalidAgeError => e
puts "Custom: #{e.message}"
endRetry
retry 从头开始重启 begin 块。用于瞬时失败(网络、速率限制),配合计数器以避免无限循环。没有限制,retry 可能挂起程序——始终保护它。
attempts = 0
begin
attempts += 1
fetch_data # might fail
rescue NetworkError
retry if attempts < 3
puts "Failed after 3 attempts"
end
# retry re-runs the begin block from the topRescue 修饰符
rescue 作为修饰符是提供回退值的简洁方式。它捕获 StandardError 并返回右侧。用于简单情况——避免用于复杂逻辑,因为它隐藏错误。非常适合解析或可选操作。
result = risky_operation rescue "default"
puts result
# Same as:
# result = begin; risky_operation; rescue; "default"; end
# Catches StandardError only
json = JSON.parse(str) rescue nil
puts "Invalid JSON" if json.nil?Throw / Catch(控制流)
throw/catch 不是异常处理——它是一种用于从深层嵌套提前退出的控制流机制(与其他语言不同)。throw :symbol 跳转到匹配的 catch。用于跳出嵌套循环;实际错误使用 begin/rescue。
catch(:done) do
[1, 2, 3, 4, 5].each do |n|
throw :done, n if n > 3
puts n
end
end
# Prints 1, 2, 3 and returns 4
# Not for errors—use for early exit from nested loops文件 I/O
读写文件
File.write/File.read 是简单的单次方法。File.open 带块自动关闭文件。File.foreach 逐行读取而不加载整个文件——对大文件节省内存。chomp 移除尾部换行符。
# Write
File.write("test.txt", "Hello, File!")
# Read
content = File.read("test.txt")
puts content # Hello, File!
# Append
File.open("log.txt", "a") { |f| f.puts "new line" }
# Read line by line
File.foreach("test.txt") { |line| puts line.chomp }文件块(自动关闭)
始终使用 File.open 带块——它保证文件被关闭,即使发生错误。块形式是处理文件的惯用、安全方式。不带块时,你必须手动调用 close。
File.open("data.txt", "w") do |f|
f.puts "Line 1"
f.puts "Line 2"
f.write("No newline")
end # file auto-closed here
File.open("data.txt", "r") do |f|
f.each_line.with_index { |line, i| puts "#{i}: #{line}" }
end文件存在与信息
File 类提供文件系统查询。file?/directory? 区分类型。mtime/ctime/atime 提供时间戳。rename/delete 修改文件系统。操作前始终检查存在以避免错误。
puts File.exist?("test.txt") # true
puts File.file?("test.txt") # true (regular file)
puts File.directory?(".") # true
puts File.size("test.txt") # bytes
puts File.mtime("test.txt") # modification time
File.rename("old.txt", "new.txt")
File.delete("new.txt") if File.exist?("new.txt")目录操作
Dir 管理目录。mkdir 创建,chdir 更改( 块形式之后恢复)。glob 匹配文件模式——* 匹配任意,** 递归匹配。适用于文件发现和批处理。
Dir.mkdir("test_dir") unless Dir.exist?("test_dir")
Dir.chdir("test_dir") do
File.write("a.txt", "a")
puts Dir.pwd # current path
end
Dir.glob("*.txt") { |f| puts f } # list .txt files
Dir.glob("**/*.rb") { |f| puts f } # recursiveCSV 与 JSON
CSV 和 JSON 在标准库中。CSV.foreach 流式传输行(节省内存)。JSON.parse 返回带字符串键的哈希/数组。to_json 序列化任何对象。这些是数据交换的基础。
require 'csv'
require 'json'
CSV.write("data.csv", [["a", 1], ["b", 2]])
CSV.foreach("data.csv") { |row| puts row.inspect }
data = { name: "Alice", age: 30 }
File.write("data.json", data.to_json)
parsed = JSON.parse(File.read("data.json"))
puts parsed["name"] # Alice日期/时间与正则表达式
Time 与 Date
Time 表示时刻(带时区)。Date 表示日历日期(无时间)。Time 上的算术使用秒。strftime 用 %Y(年)、%m(月)、%d(日)、%H:%M(时间)格式化。ISO8601 解析需要 require 'time'。
require 'time'
now = Time.now
puts now # 2024-01-15 14:30:00 +0800
puts now.strftime("%Y-%m-%d %H:%M") # 2024-01-15 14:30
tomorrow = now + 86400 # +1 day (seconds)
puts tomorrow.strftime("%A") # weekday name
date = Date.today
puts date.next_day # tomorrow日期解析与算术
日期算术自然工作——加整数加天数。next_month/prev_month 处理月边界。Date - Date 返回 Rational(天)。upto/downto 遍历日期范围。日历逻辑使用 Date,时间戳使用 Time。
require 'date'
d = Date.parse("2024-01-15")
puts d.year # 2024
puts d + 7 # 2024-01-22 (+7 days)
puts d.next_month # 2024-02-15
diff = (Date.today - d).to_i
puts "#{diff} days since"
puts Date.today.upto(Date.today + 6).map(&:wday)正则表达式匹配
=~ 返回匹配位置或 nil。$1、$2 在匹配后保存捕获组。.match 返回 MatchData 对象以获取更多细节。简单检查使用 =~,提取捕获使用 .match。正则字面量使用 /pattern/。
s = "Phone: 123-4567"
if s =~ /(\d+)-(\d+)/
puts "Area: #{$1}, Number: #{$2}"
end
m = /\w+@(\w+)/.match("[email protected]")
puts m[1] # example
puts "abc123" =~ /\d/ ? "has digit" : "no digit"正则替换
gsub 替换所有匹配(sub 替换第一个)。传递块进行动态替换。scan 将所有匹配提取到数组。替换字符串中的反向引用(\1、\2)引用捕获组。强大的文本处理工具。
s = "Hello, World"
puts s.gsub(/o/, "0") # Hell0, W0rld
puts s.gsub(/\w+/) { |w| w.capitalize }
puts s.scan(/\w+/).inspect # ["Hello", "World"]
puts "2024-01-15".gsub(/(\d+)-(\d+)-(\d+)/, '\3/\2/\1')
# => 15/01/2024正则选项
正则选项:i(不区分大小写)、m(多行——点匹配换行)、x(扩展——允许模式中的空白/注释)。%r{} 是替代分隔符,当模式包含斜杠(如 URL)时很有用。
puts /hello/i =~ "HELLO" # 0 (case-insensitive)
puts /line/m =~ "a\nb" # 0 (multiline: . matches \n)
puts /x.y/x =~ "x y" # 0 (extended: ignore whitespace)
# Common patterns
email = /[\w.]+@[\w]+\.[a-z]+/
url = %r{https?://[\w./]+}
puts email.match("[email protected]") ? "valid" : "invalid"元编程与并发
动态方法定义
define_method 在运行时创建方法。用于生成多个相似方法或构建 DSL。这是元编程——编写代码的代码。强大但谨慎使用;它会使代码更难理解。
class Dynamic
[:foo, :bar, :baz].each do |name|
define_method(name) { puts "Called #{name}" }
end
end
d = Dynamic.new
d.foo # Called foo
d.bar # Called bar
# Useful for generating similar methodsmethod_missing
method_missing 拦截对未定义方法的调用。用于构建灵活的 API 或代理。始终也重写 respond_to_missing?,以便反射工作。谨慎使用——它会隐藏 bug 并混淆静态分析。
class Proxy
def method_missing(name, *args)
puts "Called: #{name} with #{args.inspect}"
end
def respond_to_missing?(name, include_private = false)
true
end
end
p = Proxy.new
p.anything(1, 2) # Called: anything with [1, 2]线程
线程并发运行代码。join 等待完成。MRI(标准 Ruby)有 GIL,因此 CPU 密集型线程不会真正并行运行——I/O 密集型线程会。对于真正的并行,使用 Ractor(Ruby 3.0+)或多进程。
threads = [1, 2, 3].map do |n|
Thread.new { puts "Thread #{n}: #{n * n}" }
end
threads.each(&:join) # wait for all
# MRI has GIL—threads don't run truly parallel for CPU work
# Use for I/O concurrency (network, file)Fiber(协作式)
Fiber 是轻量级、协作式并发——它们通过 yield/resume 手动暂停和恢复。与线程不同,它们不并行运行。用于生成器、惰性求值或可暂停的计算。开销比线程低。
fiber = Fiber.new do
Fiber.yield 1
Fiber.yield 2
3
end
puts fiber.resume # 1
puts fiber.resume # 2
puts fiber.resume # 3
# Fibers yield control cooperatively, not preemptivelySend 与 Eval
send 按名称调用任何方法(包括私有)。public_send 尊重可见性。eval 将字符串作为 Ruby 代码执行——极其强大但对不受信任的输入很危险(代码注入)。使用 send 进行动态派发,生产中避免 eval。
class Obj
def secret; "hidden"; end
end
o = Obj.new
puts o.send(:secret) # hidden (calls private too)
puts o.public_send(:secret) # hidden (respects visibility)
x = 5
result = eval("x * 2")
puts result # 10
# eval runs a string as code—avoid with untrusted input!块、Proc 与 Lambda
块基础
块是 Ruby 最常见的闭包——通过 { } 或 do...end 传递给方法的匿名代码。yield 调用块。|n| 声明块参数。block_given? 检查是否传递了块。块不是对象(不能存储在变量中)——为此使用 Proc/Lambda。每个方法都可以接受隐式块,使 DSL 自然(Rails 大量使用这一点)。
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n } # do-end for multi-line
[1, 2, 3].each do |n|
puts n * 2
end
# yield: call the block from a method
def greet
puts "before"
yield # invokes the block
puts "after"
end
greet { puts "in block" }
# before / in block / after
# yield with arguments
def compute
result = yield(10, 20)
puts "got: #{result}"
end
compute { |a, b| a + b } # got: 30
# block_given? checks if a block was passed
def maybe_yield
return "no block" unless block_given?
yield
endProc 与 Lambda
Proc 和 Lambda 都是可调用对象(变成对象的块)。Proc 是宽松的:额外参数变为 nil,缺少的参数为 nil,'return' 退出封闭方法。Lambda 是严格的:它们检查参数数量且 'return' 仅退出 lambda。想要类方法行为时使用 lambda;想要类块行为时使用 Proc。->(stabby lambda)是现代简洁语法。
# Proc: a block stored as an object
p = Proc.new { |x| puts x * 2 }
p.call(5) # 10
p.(5) # 10 (shorthand)
p[5] # 10 (another shorthand)
# Lambda: stricter Proc
l = lambda { |x| puts x * 2 }
l = ->(x) { puts x * 2 } # stabby lambda syntax
l.call(5)
# Key differences:
# 1. Argument checking
p = Proc.new { |a, b| puts a }
p.call(1) # 1 (b is nil, no error)
l = ->(a, b) { puts a }
l.call(1) # ArgumentError (wrong number of args)
# 2. return behavior
def proc_test
p = Proc.new { return 1 }
p.call
return 2 # never reached—Proc return exits method
end
proc_test # 1
def lambda_test
l = lambda { return 1 }
l.call # returns from lambda, not method
return 2
end
lambda_test # 2方法对象
method(:name) 将方法检索为 Method 对象(像 Proc 一样可调用)。&:symbol 将符号转换为发送该方法的 proc——极其常见的惯用法:array.map(&:to_s)。& 前缀将 Proc 转换为块(或反之)。这使将方法作为参数优雅传递成为可能。方法对象保留其接收者,因此它们绑定到对象。
class Calculator
def add(a, b) a + b end
end
calc = Calculator.new
# method(:name) gets a Method object
m = calc.method(:add)
m.call(2, 3) # 5
m.(2, 3) # 5
m[2, 3] # 5
# Convert method to Proc
p = m.to_proc
p.call(4, 5) # 9
# &:method shorthand (common in map, each)
["a", "b", "c"].map(&:upcase) # ["A", "B", "C"]
# Equivalent to: .map { |s| s.upcase }
# Symbol#to_proc
:upcase.to_proc.call("hello") # "HELLOW"
# Useful for passing methods as blocks
[1, 2, 3].each(&method(:puts)) # prints 1, 2, 3闭包与绑定
闭包(块、proc、lambda)按引用捕获变量——它们能看到捕获变量的更新。这实现了有状态的闭包(计数器、累加器)。Proc#binding 访问闭包的环境(用于高级元编程)。Kernel#binding 方法捕获当前执行上下文用于 eval。闭包是 Ruby 块在回调和迭代器中如此强大的原因。
# Closures capture surrounding variables
counter = 0
increment = lambda { counter += 1 }
increment.call # 1
increment.call # 2
puts counter # 2 (closure modified it)
# Multiple closures share the same variables
x = 10
add = lambda { |n| x += n }
get = lambda { x }
add.call(5)
get.call # 15
# Proc#binding: access the closure's environment
def make_counter
count = 0
lambda { count += 1 }
end
c = make_counter
c.call # 1
# c.binding.eval("count") # 1 (peek at captured var)
# Binding for eval in a specific context
b = binding
x = 42
eval("x", b) # 42带块的自定义迭代器
包含 Enumerable 并定义 #each 即可免费获得 map、select、reduce 和 50+ 方法——这是 Ruby 的迭代器协议。each_with_object 比inject 更适合构建累加器。tap 在方 法链中插入副作用(非常适合调试)。带 yield 的自定义方法让你创建自己的 DSL(with_timing、with_database 等)。块使 Ruby 的迭代和回调模式优雅。
# Define your own iterator using yield
class LinkedList
include Enumerable # gets map, select, etc. for free
def each
node = @head
while node
yield node.value
node = node.next
end
end
end
# each_with_object (memo pattern)
result = [1, 2, 3].each_with_object({}) do |n, hash|
hash[n] = n * n
end
# {1=>1, 2=>4, 3=>9}
# tap (for debugging chains)
[1, 2, 3].map { |n| n * 2 }
.tap { |arr| puts "after map: #{arr}" }
.select { |n| n > 2 }
# Custom method with block
def with_timing
start = Time.now
yield
puts "took #{Time.now - start}s"
end
with_timing { sleep(1) }Enumerable 与迭代器
核心 Enumerable 方法
Enumerable 是 Ruby 最强大的混入——包含它并定义 #each 即可获得 50+ 方法。map 转换,select/reject 过滤,reduce/inject 聚合,find 返回第一个匹配,group_by/partition 聚类。符号简写(reduce(:+))是惯用的。这些方法适用于数组、哈希、范围、文件——任何有 #each 的对象。掌握 Enumerable 是惯用 Ruby 的关键。
nums = [1, 2, 3, 4, 5, 6]
# map/collect: transform
nums.map { |n| n * 2 } # [2, 4, 6, 8, 10, 12]
# select/filter: keep matching
nums.select { |n| n.even? } # [2, 4, 6]
nums.reject { |n| n.even? } # [1, 3, 5]
# reduce/inject: accumulate
nums.reduce(0) { |sum, n| sum + n } # 21
nums.reduce(:+) # 21 (symbol shorthand)
nums.reduce(:*) # 720
# find/detect: first match
nums.find { |n| n > 3 } # 4
# group_by: cluster
nums.group_by { |n| n.even? } # {false=>[1,3,5], true=>[2,4,6]}
# partition: split into two
nums.partition { |n| n.even? } # [[2,4,6], [1,3,5]]
# chunk: consecutive grouping
[1,1,2,2,3].chunk { |n| n }.to_a惰性求值
lazy 将可枚举转换为惰性的——值仅在需要时计算。这实现了无限序列,并避免为只需要少量结果的链计算整个集合。没有 lazy,map/select 创建中间数组。有 lazy,管道一次拉取一个值。对大型/无限数据集或昂贵转换使用 lazy。权衡:lazy 有每元素开销,因此对小集合更慢。
# Lazy: evaluate on demand (infinite sequences possible)
require 'prime'
# Without lazy: infinite loop!
# primes = (1..Float::INFINITY).select(&:prime?).first(5)
# With lazy: works!
primes = (1..Float::INFINITY).lazy.select(&:prime?).first(5)
# [2, 3, 5, 7, 11]
# Chain without intermediate arrays
(1..1_000_000).lazy
.map { |n| n * 2 }
.select { |n| n > 1_000_000 }
.first(10) # only computes what's needed
# Build a lazy enumerator
enum = Enumerator::Lazy.new(1..Float::INFINITY) do |yielder, n|
yielder << n if n.prime?
end
enum.first(5) # [2, 3, 5, 7, 11]Enumerator 与外部迭代
Enumerator 将迭代包装为对象——你可以手动调用 .next(外部迭代)而非使用块(内部迭代)。这实现了暂停/恢复迭代、窥探和创建无限序列。带块的 Enumerator.new 让你构建自定义迭代器(生成器)。大多数 Enumerable 方法在不带块调用时返回 Enumerator:[1,2,3].map 返回你可以链式的 Enumerator。
# Enumerator: an iterator object you can control
enum = [10, 20, 30].each
enum.next # 10
enum.next # 20
enum.next # 30
enum.next # StopIteration
# External iteration with loop (handles StopIteration)
enum = [1, 2, 3].each
loop do
puts enum.next
end
# Create custom Enumerator
fib = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end
fib.first(10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# Convert to array
fib.take(10).to_a排序与比较
sort 接受比较两个元素的块(返回 -1、0 或 1)。sort_by 更高效——它每个元素计算一次排序键(Schwartzian 变换)而非每次比较都计算。复杂键使用 sort_by。<=>(太空船)运算符返回 -1/0/1,是 Ruby 排序的基础。min/max/min_by/max_by 查找极值。包含 Comparable 并定义 <=> 以获得对象的自然排序。
arr = [3, 1, 4, 1, 5, 9, 2, 6]
# sort: returns new array
arr.sort # [1, 1, 2, 3, 4, 5, 6, 9]
arr.sort { |a, b| b <=> a } # descending (9, 6, 5, ...)
# sort_by: more efficient (Schwartzian transform)
arr.sort_by { |n| -n } # descending
words = ["banana", "apple", "cherry"]
words.sort_by { |w| w.length } # ["apple", "banana", "cherry"]
# min/max
arr.min # 1
arr.max # 9
arr.minmax # [1, 9]
arr.min_by { |n| n.abs } # 1
# min/max with block
people.max_by { |p| p[:age] }
# <=> (spaceship): -1, 0, 1
1 <=> 2 # -1
2 <=> 2 # 0
3 <=> 2 # 1哈希迭代与转换
哈希也是 Enumerable。each/each_pair 迭代键值对。transform_keys/transform_values(Ruby 2.5+)创建具有修改键/值的新哈希。select/reject 过滤到新哈希。merge 组合哈希(块解决冲突)。group_by 从数组构建哈希。哈希迭代是 Ruby 数据处理的基础——掌握这些以获得干净、富有表现力的数据操作。
hash = { a: 1, b: 2, c: 3 }
# Iterate
hash.each { |k, v| puts "#{k}=#{v}" }
hash.each_key { |k| puts k }
hash.each_value { |v| puts v }
hash.each_pair { |k, v| puts "#{k}: #{v}" }
# Transform keys/values
hash.transform_keys { |k| k.to_s } # {"a"=>1, "b"=>2, "c"=>3}
hash.transform_values { |v| v * 10 } # {a:10, b:20, c:30}
# Filter
hash.select { |k, v| v > 1 } # {b:2, c:3}
hash.reject { |k, v| v > 1 } # {a:1}
# Merge
{ a: 1 }.merge({ b: 2 }) # {a:1, b:2}
{ a: 1 }.merge({ a: 2 }) { |k, old, new| old + new } # {a:3}
# Invert (values become keys)
{ a: 1, b: 2 }.invert # {1=>:a, 2=>:b}
# Group arrays into hash
%w[apple apricot banana].group_by { |w| w[0] }
# {"a"=>["apple", "apricot"], "b"=>["banana"]}Gem 与 Bundler
Gem 基础
Gem 是 Ruby 包(库)。gem install 管理系统 gem。对于项目,使用 Bundler 配合 Gemfile 固定版本并管理依赖。版本说明符:'~> 1.4'(悲观,允许补丁),'>= 5.0'(乐观)。组(:development、:test、:production)让你每个环境只加载需要的 gem。require: false 表示 Bundler 不会自动 require 它(你在需要时手动 require)。
# Install a gem
$ gem install rails
$ gem install rails -v 7.0.0
$ gem install rails --pre # pre-release
# List installed gems
$ gem list
$ gem list rails
# Uninstall
$ gem uninstall rails
# Gemfile: project dependencies
# Gemfile
source 'https://rubygems.org'
gem 'rails', '7.0.0'
gem 'pg', '~> 1.4' # pessimistic: >= 1.4, < 2.0
gem 'puma', '>= 5.0' # optimistic: >= 5.0
gem 'rspec', group: :test # group-specific
gem 'pry', require: false # don't auto-require
# Groups
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :production do
gem 'newrelic_rpm'
endBundler 命令
Bundler 确保你的项目使用指定的确切 gem 版本。bundle install 读取 Gemfile 并写入 Gemfile.lock(确切版本以实现可重现——提交这个!)。bundle exec 用正确的 gem 版本运行命令(避免冲突)。bundle update 更改版本(小心——可能破坏东西)。始终使用 bundle exec 运行 rake/rspec/rails 以确保正确的 gem 加载。Gemfile.lock 使部署可重现。
# Install all gems from Gemfile
$ bundle install
$ bundle install --without production # skip group
# Update gems
$ bundle update # update all
$ bundle update rails # update specific gem
$ bundle outdated # show outdated gems
# Run commands in bundle context
$ bundle exec rails server
$ bundle exec rspec
$ bundle exec rake db:migrate
# Check for dependency issues
$ bundle check
$ bundle doctor
# Lock file: Gemfile.lock
# Records exact versions installed (for reproducibility)
# Commit Gemfile.lock to version control!
# Add/remove gems
$ bundle add rspec
$ bundle remove rspec
# Clean old gems
$ bundle cleanGemspec(创建 Gem)
.gemspec 文件定义 gem 的元数据和依赖。spec.files 列出包含的文件;require_paths 告诉 Ruby 在哪里找到它们。add_dependency 用于运行时依赖,add_development_dependency 用于测试/构建依赖。required_ruby_version 强制 Ruby 版本。gem build 创建 .gem 包;gem install 本地安装。用 gem push 发布到 rubygems.org。结构:lib/ 用于代码,spec/ 用于测试。
# my_gem.gemspec
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = "0.1.0"
spec.summary = "A useful Ruby gem"
spec.description = "Longer description..."
spec.authors = ["Alice"]
spec.email = ["[email protected]"]
spec.homepage = "https://github.com/alice/my_gem"
spec.license = "MIT"
spec.files = Dir["lib/**/*.rb"] + ["README.md"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 3.0"
spec.add_dependency "httparty", "~> 0.21"
spec.add_development_dependency "rspec", "~> 3.12"
end
# Directory structure:
# my_gem/
# lib/my_gem.rb (main file)
# lib/my_gem/version.rb
# my_gem.gemspec
# Gemfile
# spec/ (tests)
# Build and install
$ gem build my_gem.gemspec # creates my_gem-0.1.0.gem
$ gem install ./my_gem-0.1.0.gemRake 任务
Rake 是 Ruby 的 make——任务运行器。用 task :name do ... end 定义任务。命名空间分组相关任务。文件任务有依赖(源更改时重建)。sh 运行 shell 命令。Rake 用于测试、构建、部署和数据库任务(Rails 大量使用它)。用 rake task_name 运行。默认任务在你只输入 rake 时运行。用 rake greet[Alice] 传递参数。
# Rakefile
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
# Custom task
task :greet, [:name] do |t, args|
puts "Hello, #{args.name}!"
end
# $ rake greet[Alice]
# Namespace
namespace :db do
task :migrate do
puts "migrating..."
end
task :seed do
puts "seeding..."
end
end
# $ rake db:migrate
# File tasks (build dependencies)
file 'output.txt' => 'input.txt' do |t|
sh "cp #{t.source} #{t.name}"
end
# Multi-line task
task :deploy do
sh 'git push heroku main'
sh 'heroku run rails db:migrate'
endRbenv 与 RVM(Ruby 版本)
rbenv 和 RVM 在一台机器上管理多个 Ruby 版本。rbenv 轻量(shim);RVM 更重(覆盖 shell 命令)。.ruby-version 文件(提交)确保每个人都使用相同的 Ruby 版本。Gemset(RVM)或 bundle config path 隔离项目 gem。对于生产,使用 bundle config set path 将 gem 本地安装到项目,避免系统 gem 污染。始终在 .ruby-version 中固定 Ruby 版本。
# rbenv: lightweight Ruby version manager
$ rbenv install 3.2.0 # install a version
$ rbenv global 3.2.0 # set global version
$ rbenv local 3.1.0 # set per-project (.ruby-version)
$ rbenv versions # list installed
# .ruby-version file (committed to project)
# 3.2.0
# RVM: alternative version manager
$ rvm install 3.2.0
$ rvm use 3.2.0
$ rvm gemset create myapp # isolated gem sets
$ rvm use 3.2.0@myapp
# Bundler config
$ bundle config set path 'vendor/bundle' # install locally
$ bundle config set without 'development test' # for production
# Check versions
$ ruby -v
$ gem -v
$ bundle -vRails 基础
MVC 结构
Rails 是 MVC 框架:模型(ActiveRecord)处理数据,控制器(ActionController)处理 HTTP 请求,视图(ActionView)渲染响应。resources 自动生成 7 个 RESTful 路由。路由将 URL 映射到控制器动作。约定优于配置:将模型命名为 User,控制器命名为 UsersController,Rails 就会连接一切。这种结构是每个 Rails 应用的骨干。
# Rails follows Model-View-Controller
# app/
# models/ (ActiveRecord: data + business logic)
# controllers/ (ActionController: handle requests)
# views/ (ActionView: render responses)
# helpers/ (view helpers)
# config/routes.rb (URL routing)
# Route → Controller → Model → View
# GET /users → UsersController#index → User.all → index.html.erb
# config/routes.rb
Rails.application.routes.draw do
resources :users # generates 7 RESTful routes
# GET /users → index
# GET /users/new → new
# POST /users → create
# GET /users/:id → show
# GET /users/:id/edit → edit
# PATCH /users/:id → update
# DELETE /users/:id → destroy
root 'pages#home' # root path
get 'about', to: 'pages#about' # custom route
endActiveRecord 模型
ActiveRecord 是 Rails 的 ORM——模型映射到数据库表。validates 强制数据完整性。关联(has_many、belongs_to、has_one)定义关系。回调(before_save、after_create)挂钩到生命周期。作用域是可复用的查询片段。ActiveRecord 使用约定:User 模型 → users 表,created_at/updated_at 列。它是 Rails 的核心——掌握它以进行有效的 Rails 开发。
# app/models/user.rb
class User < ApplicationRecord
# Validations
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, uniqueness: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
# Associations
has_many :posts, dependent: :destroy
has_one :profile
belongs_to :company
has_and_belongs_to_many :tags
# Callbacks
before_save :normalize_email
after_create :send_welcome_email
# Scopes (reusable queries)
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
private
def normalize_email
self.email = email.downcase
end
end
# Usage
User.create(name: "Alice", email: "[email protected]")
User.active.recent
User.find(1)
User.where("age > ?", 18)控制器与强参数
控制器处理 HTTP 请求并协调模型/视图。before_action 运行过滤器(认证、加载资源)。强参数(permit)防止批量赋值漏洞——只有白名单字段可以设置。redirect_to 将用户发送到其他地方;render 显示视图。@instance 变量在视图中可用。RESTful 动作(index、show、new、create、edit、update、destroy)是约定的。
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: %i[show edit update destroy]
def index
@users = User.all
end
def show; end # @user set by before_action
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User created'
else
render :new, status: :unprocessable_entity
end
end
private
def set_user
@user = User.find(params[:id])
end
# Strong parameters: whitelist allowed fields
def user_params
params.require(:user).permit(:name, :email, :age)
end
end视图与辅助方法
ERB(嵌入式 Ruby)是 Rails 的默认模板:<%= %> 输出,<% %> 执行。link_to/button_to 生成 HTML 链接/表单。form_with 构建绑定到模型的表单。Partials(_form.html.erb)是用 render 渲染的可复用视图片段。路径辅助方法(new_user_path、user_path(user))从路由生成 URL。辅助方法保持视图整洁。对于复杂逻辑,使用视图辅助方法(app/helpers/)或装饰器。
<!-- app/views/users/index.html.erb -->
<h1>Users</h1>
<%= link_to "New User", new_user_path %>
<ul>
<% @users.each do |user| %>
<li>
<%= link_to user.name, user_path(user) %>
(<%= user.email %>)
<%= link_to "Edit", edit_user_path(user) %>
<%= button_to "Delete", user, method: :delete %>
</li>
<% end %>
</ul>
<!-- Partials: reusable view fragments -->
<!-- app/views/users/_form.html.erb -->
<%= form_with model: user do |form| %>
<% if user.errors.any? %>
<div class="errors">
<% user.errors.full_messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
</div>
<% end %>
<%= form.text_field :name %>
<%= form.email_field :email %>
<%= form.submit %>
<% end %>
<!-- Render partial -->
<%= render "form", user: @user %>迁移与数据库
迁移随时间演进数据库模式,受版本控制。create_table 定义表;add_column/remove_column 修改它们。t.references 创建外键。t.timestamps 添加 created_at/updated_at。db:migrate 应用待处理的迁移;db:rollback 撤销最后一个。schema.rb 文件是当前模式的权威来源(自动生成)。切勿直接编辑 schema.rb——使用迁移。这使数据库更改在环境之间可重现。
# Generate a migration
$ rails generate migration CreateUsers name:string email:string
# db/migrate/20240101_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: { unique: true }
t.integer :age, default: 0
t.text :bio
t.references :company, foreign_key: true
t.timestamps # created_at, updated_at
end
end
end
# Run migrations
$ rails db:migrate
$ rails db:rollback # undo last migration
$ rails db:seed # load seed data
$ rails db:reset # drop + create + migrate + seed
# Schema file (don't edit manually)
# db/schema.rb reflects current database structure
# Add a column later
$ rails generate migration AddAgeToUsers age:integer
# creates: add_column :users, :age, :integerRSpec 测试
基本语法(describe、it、expect)
RSpec 是 Ruby 的主流测试框架。describe 分组相关测试;it 定义单个测试。expect(...).to / not_to 进行断言。let 定义惰性记忆化变量(首次访问时计算一次)。context 是 describe 的别名,用于分支(when...)。shoulda-matchers gem 为常见 Rails 验证/关联提供单行语法。测试放在 spec/ 中镜像 app/ 结构。
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
# Setup with let (lazy, memoized)
let(:user) { User.new(name: "Alice", email: "[email protected]") }
describe '#name' do
it 'returns the name' do
expect(user.name).to eq("Alice")
end
# Multiple expectations with context
context 'when name is blank' do
let(:user) { User.new(name: "") }
it 'is invalid' do
expect(user).not_to be_valid
end
end
end
# One-liner syntax
it { should validate_presence_of(:email) }
it { should have_many(:posts) }
end模拟与存根
存根(allow)替换方法返回值;模拟(expect)验证方法被调用。Double 是用于测试的假对象(比真实对象快)。使用存根将被测代码与外部依赖(API、数据库)隔离。使用模拟验证交互。过度模拟使测试脆弱——足够快时优先使用真实对象。FactoryBot 创建测试数据;使用 create(保存到数据库)或 build(仅内存)。
RSpec.describe PaymentService do
let(:user) { create(:user) }
it 'charges the card' do
# Stub: replace a method's return value
allow(user).to receive(:premium?).and_return(true)
# Mock: expect a method to be called
expect(Stripe::Charge).to receive(:create).with(
amount: 1000, currency: 'usd'
).and_return(double(id: 'ch_123'))
service = PaymentService.new(user)
result = service.charge(10)
expect(result).to eq('ch_123')
end
it 'handles failure' do
# Stub to raise an error
allow(Stripe::Charge).to receive(:create)
.and_raise(Stripe::StripeError.new("card declined"))
service = PaymentService.new(user)
expect { service.charge(10) }.to raise_error(PaymentError)
end
end
# Double: test stand-in for an object
fake_card = double('Card', last4: '4242', brand: 'Visa')FactoryBot 与夹具
FactoryBot 创建具有合理默认值的测试对象。Trait 创建变体(:admin、:inactive)。序列生成唯一值(电子邮件)。create 持久化到数据库;build 不会。create_list 创建多个。通过传递覆盖任何属性。工厂比夹具(YAML)更灵活但更慢(数据库写入)。使用 build_stubbed 进行不触及数据库的快速测试。保持工厂简单——复杂工厂表明复杂模型。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Alice" }
email { "[email protected]" }
age { 30 }
active { true }
# Traits: variations
trait :admin do
role { "admin" }
end
trait :inactive do
active { false }
end
# Associations
company { association(:company) }
# Sequences for unique values
sequence(:email) { |n| "user#{n}@example.com" }
end
end
# Usage in specs
let(:user) { create(:user) } # saved to DB
let(:admin) { create(:user, :admin) } # with trait
let(:user) { build(:user) } # not saved
let(:users) { create_list(:user, 5) } # 5 users
# Override attributes
let(:alice) { create(:user, name: "Alice") }Before/After 钩子
before 钩子运行设置代码:before(:each)(最常见)在每个测试之前,before(:all) 每组一次。after 钩子清理。DatabaseCleaner 管理测试数据库状态(事务用于速度,截断用于彻底)。优先使用 let 而非 before(:each)——let 是惰性的(仅在使用时计算)且记忆化的,而 before 即使测试不需要也会运行。对于必须发生的副作用(日志记录、时间冻结)使用 before。
RSpec.describe User do
before(:all) do
# Runs once before all tests in this group
@shared_data = load_expensive_data
end
before(:each) do # or just 'before'
# Runs before each test
@user = User.create(name: "test")
end
after(:each) do
# Runs after each test (cleanup)
User.delete_all
end
after(:all) do
# Runs once after all tests
@shared_data = nil
end
# Database cleaner for transactional tests
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
end
# Use before for setup, after for cleanup
# Prefer let over before(:each) for lazy evaluation集成与系统测试
请求规范通过 HTTP 测试整个堆栈(路由 → 控制器 → 模型 → 视图)。系统规范(Capybara)驱动真实浏览器——填写表单、点击、检查页面内容。单元测试(模型规范)快速且隔离;集成/系统测试较慢但能捕获连接 bug。使用测试金字塔:许多快速单元测试,较少集成测试,最少系统测试。have_http_status 检查响应代码;visit/fill_in/click_button 驱动浏览器。
# spec/requests/users_spec.rb (integration)
require 'rails_helper'
RSpec.describe 'Users API', type: :request do
describe 'GET /users' do
before { create_list(:user, 3) }
it 'returns all users' do
get '/users'
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /users' do
it 'creates a user' do
post '/users', params: { user: { name: 'Bob' } }
expect(response).to have_http_status(:created)
end
end
end
# spec/system/login_spec.rb (browser tests)
require 'rails_helper'
RSpec.describe 'Login', type: :system do
it 'logs in a user' do
user = create(:user, password: 'secret')
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'secret'
click_button 'Log in'
expect(page).to have_content('Welcome')
end
end文件与目录操作
读取文件
File.read 将整个文件加载到内存——适用于小文件。File.foreach 逐行读取——对大文件必不可少(不会耗尽内存)。File.open 带块自动关闭文件(RAII)。readlines 返回行数组(带换行符——使用 chomp)。binread 用于二进制文件。如果文件可能不存在,读取前始终检查 File.exist?,或 rescue Errno::ENOENT。
# Read entire file
content = File.read("data.txt")
# Read line by line (memory efficient)
File.foreach("large.txt") do |line|
puts line.chomp # chomp removes trailing newline
end
# Read all lines into array
lines = File.readlines("data.txt")
# With a block (auto-closes)
File.open("data.txt") do |f|
f.each_line { |line| puts line }
end
# Read with options
File.read("data.txt", encoding: "utf-8")
File.binread("image.png") # binary mode
# Check existence
File.exist?("data.txt")
File.file?("data.txt") # is it a regular file?
File.directory?("path") # is it a directory?写入文件
File.write 是写入文件的最简单方式(默认覆盖)。使用 mode: 'a' 追加。File.open 带块确保即使发生异常文件也会关闭。puts 添加换行符;write 不会。<< 是 write 的别名(Ruby 中常见)。对于日志,打开一次并多次写入(为性能缓冲)。始终关闭文件或使用块形式以避免资源泄漏。
# Write (overwrites)
File.write("output.txt", "Hello, World!")
# Append
File.write("output.txt", "More text\n", mode: "a")
# With a block (buffered, auto-closes)
File.open("log.txt", "w") do |f|
f.puts "Line 1"
f.puts "Line 2"
f.write("No newline")
f << "appended" # << is an alias for write
end
# Binary write
File.binwrite("data.bin", binary_data)
# Modes: r (read), w (write/truncate), a (append),
# r+ (read/write), w+ (read/write/truncate),
# b (binary, Windows)
# Flush buffer
f = File.open("log.txt", "w")
f.write("data")
f.flush # write without closing
f.close目录操作
Dir.glob 带模式查找文件(** 用于递归)。FileUtils 提供健壮的文件操作:mkdir_p 创建嵌套目录,cp_r 递归复制,rm_rf 强力移除(小心!)。Dir.chdir 更改工作目录(使用块形式临时更改)。Dir.entries 包含 . 和 ..;glob 不会。跨平台安全优先使用 FileUtils 而非手动 File 操作。
require 'fileutils'
# List directory contents
Dir.entries(".") # [".", "..", "file.txt", ...]
Dir.glob("*.rb") # ["script.rb", "test.rb"]
Dir.glob("**/*.rb") # recursive
Dir.glob("src/**/*.{rb,erb}") # multiple extensions
# Create directories
Dir.mkdir("new_dir")
Dir.mkdir("nested/path") rescue Errno::ENOENT # fails if parent missing
FileUtils.mkdir_p("nested/path/deep") # creates all parents
# Remove
Dir.rmdir("empty_dir") # only works if empty
FileUtils.rm_rf("dir") # recursive force (dangerous!)
# Copy/Move
FileUtils.cp("a.txt", "b.txt")
FileUtils.mv("old.txt", "new.txt")
FileUtils.cp_r("src_dir", "dest_dir") # recursive
# Change directory
Dir.chdir("/tmp") { puts Dir.pwd } # temporarily
Dir.chdir("/tmp") # permanently for process
# Check if directory
File.directory?("path")
File.exist?("path")Pathname 与 Tempfile
Pathname 是文件路径的面向对象包装器——比字符串操作更干净。它提供 dirname、basename、extname、join 和文件检查作为方法。Tempfile 创建自动删除的临时文件(使用块形式)。Dir.mktmpdir 创建临时目录。使用这些进行干净、安全的路径处理和临时文件管理。Pathname 跨平台安全组合路径(处理 / vs \)。
require 'pathname'
require 'tempfile'
# Pathname: object-oriented path manipulation
path = Pathname.new("/home/user/docs/file.txt")
path.dirname # #<Pathname:/home/user/docs>
path.basename # #<Pathname:file.txt>
path.extname # ".txt"
path.parent # #<Pathname:/home/user/docs>
path.join("sub", "file.rb") # /home/user/docs/sub/file.rb
path.exist?
path.directory?
path.file?
path.readable?
# Tempfile: auto-deleted file
Tempfile.create("prefix") do |f|
f.write("temporary data")
f.rewind
puts f.read
end # file deleted after block
# Tempfile without block (must close/unlink manually)
tf = Tempfile.new("prefix")
tf.write("data")
tf.close
tf.unlink # delete
# Dir.mktmpdir for temp directories
Dir.mktmpdir do |dir|
# work in dir
end # dir deleted afterCSV 与 JSON
CSV 和 JSON 内置于 Ruby 标准库。CSV.foreach 逐行读取(节省内存);CSV.read 加载所有内容。headers: true 将第一行视为列名。JSON.parse 将 JSON 转换为 Ruby 哈希/数组;to_json 序列化 Ruby 对象。symbolize_names 提供符号键(更干净)。对于 YAML,使用 require 'yaml' 和 YAML.load_file。这些是 Ruby 脚本和 Web 应用中数据交换的基础。
require 'csv'
require 'json'
# CSV reading
CSV.foreach("data.csv", headers: true) do |row|
puts row['name'] # access by header
puts row[0] # access by index
end
# CSV reading all at once
rows = CSV.read("data.csv", headers: true)
rows.first['name'] # first row's name
# CSV writing
CSV.open("output.csv", "w") do |csv|
csv << ["name", "age"] # header
csv << ["Alice", 30]
csv << ["Bob", 25]
end
# JSON
data = { name: "Alice", age: 30 }
json = data.to_json # '{"name":"Alice","age":30}'
parsed = JSON.parse(json) # {"name"=>"Alice", "age"=>30}
# JSON with symbols
JSON.parse(json, symbolize_names: true) # {name: "Alice", age: 30}
# Read/write JSON files
File.write("data.json", data.to_json)
loaded = JSON.parse(File.read("data.json"))编码与字符串内部
字符串编码
Ruby 字符串携带其编码(通常为 UTF-8)。force_encoding 将字节重新解释为不同编码(无转换——当你知道字节已经是该编码时使用)。encode 实际在编码之间转换。valid_encoding? 检查字节对字符串编码是否有效。编码问题导致可怕的 Encoding::CompatibilityError。始终知道数据的编码;默认为 UTF-8。
# Ruby strings have an encoding
"hello".encoding # #<Encoding:UTF-8>
"café".encoding # #<Encoding:UTF-8>
# Default external encoding (for file I/O)
Encoding.default_external # #<Encoding:UTF-8>
# Force an encoding (reinterprets bytes, doesn't convert)
bytes = "café".bytes # [99, 97, 102, 195, 169]
latin1 = bytes.pack("C*").force_encoding("ISO-8859-1")
# "caf" + é (as single byte)
# Convert encoding (transcodes)
utf8 = "café"
latin1 = utf8.encode("ISO-8859-1") # converts
back = latin1.encode("UTF-8") # converts back
# Check if valid
"abc".valid_encoding? # true
"\xff".valid_encoding? # false (invalid byte)
# Common encodings: UTF-8, ASCII, ISO-8859-1, Windows-1252
# Always use UTF-8 unless you have a specific reason编码转换与 I/O
文件 I/O 使用 Encoding.default_external 进行读取。用 encoding: 选项按文件指定编码。'source:target' 语法(ISO-8859-1:UTF-8)以源编码读取并转换为目标编码。设置 default_internal 使 Ruby 自动将所有读取的字符串转换为该编码。对于 Web 应用,一切都应为 UTF-8。处理遗留数据时,显式指定编码以避免损坏。
# Read a file with specific encoding
content = File.read("data.txt", encoding: "ISO-8859-1")
# content.encoding is ISO-8859-1
# Read and convert to UTF-8
content = File.read("data.txt", encoding: "ISO-8859-1:UTF-8")
# The "source:target" syntax converts while reading
# Write with specific encoding
File.write("output.txt", "café", encoding: "UTF-8")
# Open with encoding
File.open("data.txt", "r:ISO-8859-1") do |f|
f.read # ISO-8859-1 encoded string
end
# Convert while opening
File.open("data.txt", "r:ISO-8859-1:UTF-8") do |f|
f.read # UTF-8 encoded string
end
# Set default encodings
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8字符串方法深入
Ruby 字符串有丰富的方法。检查(length、include?、start_with?)检查属性。转换方法返回新字符串(Ruby 中字符串是可变的,但这些不会修改)。子字符串访问使用 [start, length] 或范围。sub 替换第一个匹配;gsub 替换所有(支持正则和块)。split/join 在字符串和数组之间转换。注意:Ruby 3.0+ frozen_string_literal 编译指示使字符串不可变以提升性能——使用 << 或 + 构建。
s = "Hello, World"
# Inspection
s.length # 12
s.empty? # false
s.include?("World") # true
s.start_with?("Hello") # true
s.end_with?("World") # true
# Transformation (return new string)
s.upcase # "HELLO, WORLD"
s.downcase # "hello, world"
s.capitalize # "Hello, world"
s.swapcase # "hELLO, wORLD"
s.reverse # "dlroW ,olleH"
s.strip # remove leading/trailing whitespace
s.chomp(",") # remove trailing substring
s.chop # remove last char
# Substrings
s[0, 5] # "Hello" (start, length)
s[7..11] # "World" (range)
s[-5..] # "World" (negative index)
# Replace
s.sub("World", "Ruby") # first match
s.gsub("o", "0") # all matches
s.gsub(/\w+/) { |w| w.upcase } # with block
# Split/Join
"a,b,c".split(",") # ["a", "b", "c"]
["a", "b"].join("-") # "a-b"冻结字符串与性能
frozen_string_literal: true(文件顶部的魔术注释)使所有字符串字面量不可变——这是一种性能优化(冻结字符串可以共享内存)并防止意外修改 bug。对于构建字符串,使用 <<(原地追加,O(n))而非 +=(每次创建新字符串,O(n²))。join 对数组最干净。StringIO 像文件一样但写入字符串——适用于构建复杂输出。Ruby 3.x 鼓励默认冻结字符串。
# frozen_string_literal: true (at top of file)
# Makes all string literals in the file frozen (immutable)
# Mutable vs frozen
s = "hello"
s << " world" # "hello world" (mutates)
f = "hello".freeze
# f << " world" # Error: can't modify frozen String
# Why freeze? Performance: frozen strings share memory
# "abc".freeze is the same object everywhere
"a".freeze.equal?("a".freeze) # true (same object)
# Building strings efficiently
# BAD: creates many intermediate strings
result = ""
items.each { |i| result += i.to_s } # O(n²)
# GOOD: use << or join
result = ""
items.each { |i| result << i.to_s } # O(n)
result = items.map(&:to_s).join # cleanest
# StringIO for buffered building
require 'stringio'
io = StringIO.new
io << "line 1\n"
io << "line 2\n"
result = io.string符号与字符串
符号(:name)是不可变的、单例标识符——内存中永远只有一个 :foo。字符串是可变文本数据,有多个实例。哈希键(更快的相等性检查)、方法名和类枚举值使用符号。实际文本使用字符串。符号作为哈希键和比较略快。在现代 Ruby(2.2+)中,符号可以被垃圾回收,因此旧的'符号内存泄漏'担忧已消失。Rails 大量使用符号作为键和状态值。
# Symbol: immutable, reusable identifier
:hello
:world
status = :active
# String: mutable, can have many instances
"hello"
"world"
status = "active"
# Key difference: identity
"hello".equal?("hello") # false (different objects)
:hello.equal?(:hello) # true (same object, singleton)
# Memory: symbols are singletons
1000.times { :foo } # one :foo object
1000.times { "foo" } # 1000 "foo" objects (unless frozen)
# Use symbols for:
# - Hash keys (faster comparison)
# - Method names (send(:method_name))
# - Enum-like values (:active, :pending, :closed)
# - Identifiers (not text data)
# Use strings for:
# - Text data (names, content)
# - Things that change
# Conversion
:hello.to_s # "hello"
"hello".to_sym # :hello
"hello world".to_sym # :"hello world" (valid but ugly)模块与混入
模块基础
模块有两个目的:命名空间(分组相关代码,防止名称冲突)和混入(无继承共享行为)。模块方法(def self.method)在模块上调用。实例方法(def method)用于混入类。模块不能被实例化。使用模块为类命名空间(MyApp::User)并组织常量和实用函数。这是 Ruby 对多重继承的替代方案。
# Module: a namespace + collection of methods
module MathUtils
PI = 3.14159
def self.circle_area(radius) # module method
PI * radius ** 2
end
def square(x) # instance method (for mixins)
x * x
end
end
# Access constants and module methods
MathUtils::PI # 3.14159
MathUtils.circle_area(5) # 78.54
# Namespacing classes
module MyApp
class User
# MyApp::User
end
end
# Prevent instantiation (modules can't be instantiated)
# MathUtils.new # NoMethodErrorInclude 与 Extend 与 Prepend
三种混入模块的方式:include(添加实例方法,在查找中位于类之下),extend(添加类方法),prepend(添加实例方法,位于类之上——可以包装/重写)。prepend 对于 before/after 钩子很强大(调用 super 调用原始方法)。方法查找:prepend → 类 → include → 父类。普通混入使用 include,需要包装现有方法时使用 prepend,类级功能使用 extend。
module Greetable
def greet
"Hello from #{self.class}"
end
end
class User
include Greetable # adds as instance methods
end
User.new.greet # "Hello from User"
class Service
extend Greetable # adds as class methods
end
Service.greet # "Hello from Service"
# Method lookup order:
# prepend → class → include → super
module Logging
def save
puts "before save"
super # calls the original save
puts "after save"
end
end
class Record
prepend Logging # Logging#save runs first
def save; puts "saving"; end
end
Record.new.save
# before save / saving / after saveEnumerable 混入
包含 Enumerable 并定义 #each 即可获得 map、select、reduce、sort、min、max 和 40+ 更多方法——这是 Ruby 的迭代器协议。包含 Comparable 并定义 <=> 即可获得 <、>、==、between?、clamp 和排序支持。这些混入是 Ruby 集合如此强大的原因。任何表示集合或具有自然排序的类都应包含这些。这是组合优于继承。
# Include Enumerable + define #each = 50+ methods free
class Playlist
include Enumerable
def initialize(songs)
@songs = songs
end
def each
@songs.each { |song| yield song }
end
end
playlist = Playlist.new(["Song A", "Song B", "Song C"])
playlist.map { |s| s.upcase } # ["SONG A", "SONG B", "SONG C"]
playlist.select { |s| s.include?("A") } # ["Song A"]
playlist.reduce(:+) # "Song ASong BSong C"
playlist.sort # ["Song A", "Song B", "Song C"]
playlist.first(2) # ["Song A", "Song B"]
playlist.include?("Song B") # true
playlist.count # 3
# Comparable: define <=> for natural ordering
class Temperature
include Comparable
attr_reader :celsius
def initialize(c) @celsius = c end
def <=>(other) celsius <=> other.celsius end
end
t1 = Temperature.new(20)
t2 = Temperature.new(30)
t1 < t2 # true (uses <=>)
t1 == t2 # false单例方法与类方法
单例方法属于一个特定对象。类方法只是类对象上的单例方法。'class << self' 打开单例类(本征类)以干净地定义多个类方法。单例模式使用类变量持有一个实例。理解单例类是 Ruby 对象模型的关键——每个对象都有一个持有其唯一方法的单例类。这实现了每对象自定义和元编程。
# Singleton method: defined on one object only
str = "hello"
def str.shout
upcase + "!"
end
str.shout # "HELLO!"
# "world".shout # NoMethodError (only str has it)
# Class methods are singleton methods on the class
class Calculator
def self.add(a, b) # singleton method on Calculator
a + b
end
end
Calculator.add(1, 2) # 3
# Singleton class (eigenclass): where singleton methods live
class Calculator
class << self # opens the singleton class
def multiply(a, b) a * b end
def divide(a, b) a / b end
end
end
Calculator.multiply(2, 3) # 6
# Singleton pattern
class Logger
@instance = Logger.new
class << self
attr_reader :instance
end
end
Logger.instance.equal?(Logger.instance) # true细化(范围猴子补丁)
细化(Ruby 2.1+)允许范围猴子补丁——向现有类添加方法,但仅在你显式 'using' 细化的地方。这比全局猴子补丁(可能破坏其他代码)更安全。细化按范围(文件、类、方法)激活。它们适用于添加便利方法而不污染全局命名空间。由于性能和一些范围怪癖,它们比应有的更少见,但它们是扩展核心类的'正确'方式。
# Refinement: limited monkey patching
module StringRefinements
refine String do
def shout
upcase + "!!!"
end
def palindrome?
downcase == downcase.reverse
end
end
end
# Without using: shout is undefined
# "hello".shout # NoMethodError
# With using: refinement is active in this scope
using StringRefinements
"hello".shout # "HELLO!!!"
"racecar".palindrome? # true
# Scoped to file or module
class App
using StringRefinements
def greet
"hi".shout # works here
end
end
# "hi".shout # NoMethodError (outside App)
# Safer than global monkey patching
# Refinements only affect code that opts in块 Proc Lambda 深入
块基础
块是使用 { } 或 do...end 传递给方法的匿名闭包。yield 从方法内调用块。块可以通过 |var| 接受参数。方法可以用 block_given? 检查是否传递了块。块是 Ruby 迭代器模式和 DSL 的基础。它们从封闭作用域捕获变量(闭包)。
# Block: anonymous chunk of code passed to a method
[1, 2, 3].each { |n| puts n } # Single-line
[1, 2, 3].each do |n| # Multi-line
puts n
end
# yield invokes the block
def greet
print "Hello, "
yield # Calls the block
puts "!"
end
greet { print "World" } # Hello, World!
# yield with arguments
def calculate(a, b)
yield(a + b)
yield(a * b)
end
calculate(3, 4) { |result| puts "Result: #{result}" }Proc 与 Lambda
Proc 和 lambda 是包装块的对象,允许存储在变量中并传递。Proc 有宽松的参数检查(忽略额外参数)并从封闭方法返回。Lambda 有严格的参数检查且仅从自身返回。Proc 用于灵活性(像接受块的方法),lambda 用于具有可预测行为的匿名函数。->() {} 语法(stabby lambda)对单行很简洁。
# Proc: object wrapping a block
my_proc = Proc.new { |x| puts x * 2 }
my_proc.call(5) # 10
my_proc.(5) # 10 (shorthand)
my_proc[5] # 10 (another shorthand)
# Lambda: stricter Proc
my_lambda = lambda { |x| puts x * 2 }
my_lambda = ->(x) { puts x * 2 } # Stabby lambda
# Difference 1: argument checking
my_proc.call(1, 2, 3) # OK (ignores extra args)
my_lambda.call(1, 2, 3) # ArgumentError (wrong number of args)
# Difference 2: return behavior
def proc_test
p = Proc.new { return 1 }
p.call
return 2 # Never reached
end
def lambda_test
l = lambda { return 1 }
l.call # Returns 1 to the lambda, not the method
return 2 # Reached
end方法对象
method() 方法返回包装现有方法的 Method 对象。& 运算符将 Method 或 Proc 转换为块(反之亦然)。Symbol#to_proc 将 :upcase 转换为 { |x| x.upcase },实现了简洁的 &:symbol 语法。这是短块操作的惯用 Ruby。方法对象绑定到其接收者,因此传递时记住 self。
# Convert method to Proc with method()
class Calculator
def add(a, b) a + b end
def multiply(a, b) a * b end
end
calc = Calculator.new
add_proc = calc.method(:add)
puts add_proc.call(2, 3) # 5
# Pass method as block with & operator
def apply_op(a, b, op)
op.call(a, b)
end
puts apply_op(2, 3, calc.method(:add)) # 5
puts apply_op(2, 3, calc.method(:multiply)) # 6
# Symbol#to_proc
words = ["hello", "world"]
upcased = words.map(&:upcase) # ["HELLO", "WORLD"]
# Equivalent to: words.map { |w| w.upcase }
# Common with &:map(&:to_i), select(&:positive?), sort_by(&:length)闭包与变量
闭包按引用而非值捕获变量。多个闭包可以共享状态(计数器示例)。块局部变量(在参数中 ; 之后声明)遮蔽外部变量而不修改它们。这实现了累加器、生成器和记忆化等函数式模式。注意:持有引用的闭包如果不释放可能导致内存泄漏。使用此模式进行私有状态封装。
# Blocks/Procs/Lambdas capture variables (closures)
counter = 0
increment = lambda { counter += 1 }
increment.call
increment.call
puts counter # 2
# Multiple closures sharing state
def make_counters
count = 0
[
lambda { count += 1 },
lambda { count },
lambda { count = 0 }
]
end
inc, get, reset = make_counters
inc.call; inc.call
puts get.call # 2
reset.call
puts get.call # 0
# Block-local variables (shadow outer)
x = 10
[1, 2, 3].each do |y; x| # x is block-local
x = y * 2
end
puts x # 10 (unchanged)自定义迭代器
包含 Enumerable 并实现 each 即可让你的类免费获得所有迭代器方法(map、select、reduce、sort 等)。用 &block 传递块并调用它,或使用 yield。& 运算符将块转换为 Proc 并转换回来。这是使自定义集合可迭代的惯用方式。实现 each 进行前向迭代;添加 reverse_each 进行双向。
class Tree
include Enumerable
def initialize(value, children = [])
@value = value
@children = children
end
def each(&block)
block.call(@value)
@children.each { |child| child.each(&block) }
end
end
tree = Tree.new(1, [
Tree.new(2, [Tree.new(4), Tree.new(5)]),
Tree.new(3)
])
tree.each { |v| puts v } # 1 2 4 5 3
puts tree.map { |v| v * 2 }.inspect # [2, 4, 8, 10, 6]
puts tree.select(&:even?).inspect # [2, 4]
puts tree.reduce(:+) # 15
# Including Enumerable gives you map, select, reduce, etc.元编程
动态方法
method_missing 拦截对未定义方法的调用,实现动态派发。始终重写 respond_to_missing? 以匹配。define_method 在运行时创建方法,适用于生成相似方法(如 ActiveRecord 的 find_by_*)。instance_variable_get/set 按名称访问实例变量。谨慎使用元编程——它使代码更难理解和调试。可能时优先使用显式定义。
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
# Method missing: catch undefined method calls
def method_missing(name, *args, &block)
if name.to_s =~ /^(.*)_with_prefix$/
attr_name = $1
"PREFIX_#{send(attr_name)}"
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s =~ /^(.*)_with_prefix$/ || super
end
end
p = Person.new("Alice", 30)
puts p.name_with_prefix # PREFIX_Alice
# Define methods dynamically
class Person
[:home, :work, :mobile].each do |type|
define_method("#{type}_phone") do
instance_variable_get("@#{type}_phone")
end
define_method("#{type}_phone=") do |value|
instance_variable_set("@#{type}_phone", value)
end
end
end开放类与猴子补丁
Ruby 类是开放的:你可以向任何类添加方法,包括像 String 这样的内置类。这很强大但危险(猴子补丁可能破坏其他代码)。细化(Ruby 2.1+)提供范围猴子补丁:它们仅在使用该模块的文件/类中应用。优先使用细化而非全局猴子补丁以获得更安全的元编程。清楚地记录补丁并避免更改核心行为。
# Reopen existing classes (monkey patching)
class String
def shout
upcase + "!"
end
def word_count
split.size
end
end
puts "hello world".shout # HELLO WORLD!
puts "one two three".word_count # 3
# Refinements: scoped monkey patches
module ShoutRefinement
refine String do
def shout
upcase + "!"
end
end
end
class MyClass
using ShoutRefinement
def greet(name)
"hello #{name}".shout # Works here
end
end
# "test".shout # NoMethodError (refinement not active)钩子与回调
Ruby 提供生命周期钩子:inherited(创建子类)、included(包含模块)、prepended(前置模块)、method_added(定义方法)、method_removed、method_undefined。这些使框架能够自动响应类更改。ActiveRecord 使用这些跟踪属性,Rails 使用它们进行路由。将钩子重写为类方法(self.inherited)用于类级事件,实例方法用于方法事件。
class Observable
# Hook: called when class is subclassed
def self.inherited(subclass)
puts "#{subclass} inherits from #{self}"
end
# Hook: called when a module is included
def self.included(base)
puts "#{self} included in #{base}"
end
# Hook: called when a method is added
def method_added(name)
puts "Added method: #{name}"
end
# Hook: called when method is undefined
def method_removed(name)
puts "Removed method: #{name}"
end
end
class Child < Observable
def my_method; end
end
# Output:
# Child inherits from Observable
# Added method: my_method
# Other hooks: method_undefined, extended, prependedeval 与 Binding
eval 将字符串作为 Ruby 代码执行,如果输入不受信任则危险(代码注入)。binding 捕获当前执行上下文(变量、self)用于稍后 eval。class_eval 在类上下文中执行代码(定义方法)。instance_eval 将 self 更改为接收者。用于 DSL 和代码生成,但避免对用户输入使用 eval。动态代码优先使用块和 define_method。如果 eval 不可避免,始终清理输入。
# eval: execute a string as Ruby code
result = eval("1 + 2 * 3")
puts result # 7
# eval with binding (context)
def eval_in_context(code)
x = 10
binding # Returns a Binding object capturing local variables
end
b = eval_in_context("binding")
puts eval("x * 2", b) # 20
# class_eval: execute code in class context
String.class_eval do
def palindrome?
self == reverse
end
end
puts "racecar".palindrome? # true
# instance_eval: execute in instance context
"hello".instance_eval do
puts length # 5 (self is the string)
end
# NEVER eval user input (security risk)反射与内省
Ruby 提供丰富的反射:methods 列出所有方法,instance_variables 列出实例变量,ancestors 显示继承链。parameters 揭示方法的参数名称和类型。respond_to? 检查对象是否响应方法。使用反射进行调试、序列化(检查属性)和构建通用工具(如 ORM)。避免过度使用反射——它绕过类型安全并使代码更难跟踪。
class User
attr_accessor :name, :email
def initialize(name, email)
@name = name
@email = email
end
def save; end
def valid?; true; end
end
u = User.new("Alice", "[email protected]")
# Inspect methods
puts u.methods - Object.methods # [:name, :email, :save, :valid?, ...]
puts u.public_methods(false) # Only this class's methods
puts User.instance_methods(false) # [:save, :valid?, ...]
# Inspect variables
puts u.instance_variables.inspect # [:@name, :@email]
puts u.instance_variable_get(:@name) # Alice
# Inspect class
puts User.ancestors.inspect
puts User.instance_method(:save).parameters
# Check if responds to method
puts u.respond_to?(:save) # true
puts u.is_a?(User) # true测试(RSpec/Minitest)
RSpec 基础
RSpec 是 BDD 风格的测试框架。describe 分组相关测试,context 按条件分组,it 定义单个测试。let 创建惰性记忆化变量。expect(...).to matcher 是断言语法。常用匹配器:eq、be_valid、include、raise_error。subject + is_expected 减少样板。用 rspec(全部)或 rspec path/to/spec(特定)运行。使用工厂(FactoryBot)而非夹具作为测试数据。
# spec/spec_helper.rb
require 'rspec'
require_relative '../lib/my_app'
# spec/models/user_spec.rb
require 'spec_helper'
RSpec.describe User do
# Setup with let (lazy, memoized)
let(:user) { User.new(name: 'Alice', email: '[email protected]') }
describe '#name' do
it 'returns the name' do
expect(user.name).to eq('Alice')
end
end
describe '#valid?' do
context 'with valid attributes' do
it 'is valid' do
expect(user).to be_valid
end
end
context 'without name' do
let(:user) { User.new(email: '[email protected]') }
it 'is invalid' do
expect(user).not_to be_valid
expect(user.errors[:name]).to include("can't be blank")
end
end
end
# Multiple examples
describe '#age' do
subject { user.age }
it { is_expected.to be >= 0 }
end
end
# Run: rspec spec/models/user_spec.rb
# Run all: rspecRSpec 模拟与存根
存根(allow)替换方法返回值;模拟(expect)验证方法被调用。double 创建测试替身(假对象)。and_return 设置返回值,and_raise 模拟错误,with 设置预期参数。谨慎使用模拟——过度模拟使测试脆弱。测试行为,而非实现。可行时优先使用真实对象;外部服务(API、电子邮件、支付网关)使用模拟。
RSpec.describe PaymentProcessor do
let(:gateway) { double('PaymentGateway') }
let(:processor) { PaymentProcessor.new(gateway) }
describe '#charge' do
it 'charges the gateway' do
# Stub: replace method return value
allow(gateway).to receive(:charge).and_return(success: true)
result = processor.charge(100)
expect(result[:success]).to be true
end
it 'raises on gateway error' do
allow(gateway).to receive(:charge)
.and_raise(GatewayError, 'Network failure')
expect { processor.charge(100) }
.to raise_error(GatewayError, /Network/)
end
it 'verifies the gateway was called' do
# Mock: expect method to be called
expect(gateway).to receive(:charge).with(100)
processor.charge(100)
end
it 'receives multiple calls' do
allow(gateway).to receive(:charge).and_return(true, false, true)
expect(gateway.charge(1)).to be true
expect(gateway.charge(2)).to be false
end
end
endMinitest
Minitest 是 Ruby 的默认测试库(随 Ruby 提供)。它支持 Unit 风格(assert/refute)和 Spec 风格(must_equal)语法。setup 在每个测试之前运行。断言:assert_equal、assert_nil、assert_raises、assert_includes。Minitest 比 RSpec 快且依赖更少。使用 Minitest::Mock 进行模拟,或 Mocha gem 获得更多功能。Rails 默认使用 Minitest(ActiveSupport::TestCase)。
# test/test_helper.rb
require 'minitest/autorun'
require_relative '../lib/my_app'
# test/models/user_test.rb
require 'test_helper'
class UserTest < Minitest::Test
def setup
@user = User.new(name: 'Alice', email: '[email protected]')
end
def test_name
assert_equal 'Alice', @user.name
end
def test_valid_with_attributes
assert @user.valid?
end
def test_invalid_without_name
@user.name = nil
refute @user.valid?
assert_includes @user.errors[:name], "can't be blank"
end
def test_raises_on_error
assert_raises(ArgumentError) { User.new! }
end
end
# Spec-style Minitest
class UserSpec < Minitest::Spec
let(:user) { User.new(name: 'Alice') }
it 'has a name' do
_(user.name).must_equal 'Alice'
end
end
# Run: rake test
# Run specific: ruby -Itest test/models/user_test.rb测试数据与工厂
FactoryBot 生成具有默认值、变体 trait 和关联的测试对象。create 持久化到数据库;build 不会。Trait 可组合(factory :admin_with_posts, traits: [:admin, :with_posts])。工厂比夹具更灵活但更慢。夹具(YAML)更快但不太灵活。根据需要选择:简单数据用夹具,复杂关系用工厂。避免有太多 trait 的工厂——保持它们专注。
# FactoryBot (gem 'factory_bot')
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { 'Alice' }
email { '[email protected]' }
age { 30 }
trait :admin do
role { 'admin' }
end
trait :with_posts do
after(:create) do |user|
create_list(:post, 3, user: user)
end
end
factory :admin_user, traits: [:admin]
factory :user_with_posts, traits: [:with_posts]
end
end
# Usage in specs
let(:user) { create(:user) } # Saved to DB
let(:admin) { create(:admin_user) }
let(:user_with_posts) { create(:user_with_posts) }
let(:unsaved_user) { build(:user) } # Not saved
# Fixtures (alternative)
# test/fixtures/users.yml
# alice:
# name: Alice
# email: [email protected]
# In tests: users(:alice)集成与系统测试
请求规范通过 HTTP 测试整个堆栈(路由、控制器、模型)。系统规范(Capybara)驱动真实浏览器,测试 JavaScript 交互。API 端点使用请求规范,用户流程使用系统规范。have_http_status 检查响应代码。visit/fill_in/click_button 模拟用户操作。系统测试较慢但能捕获集成 bug。用 js: true 运行 JavaScript 驱动页面的系统测试。
# spec/requests/users_spec.rb (integration)
require 'spec_helper'
RSpec.describe 'Users API', type: :request do
describe 'GET /users' do
before { create_list(:user, 3) }
it 'returns all users' do
get '/users', as: :json
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(3)
end
end
describe 'POST /users' do
it 'creates a user' do
post '/users', params: { user: { name: 'Bob', email: '[email protected]' } }
expect(response).to have_http_status(201)
expect(User.last.name).to eq('Bob')
end
end
end
# System tests (browser automation)
# spec/system/login_spec.rb
require 'spec_helper'
RSpec.describe 'Login', type: :system do
it 'logs in a user' do
user = create(:user, password: 'password123')
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password123'
click_button 'Log In'
expect(page).to have_content('Welcome')
end
end线程与 Fiber
线程基础
Ruby(MRI)中的线程是由 VM 调度的绿色线程——由于 GIL(全局解释器锁),它们不会真正并行运行。但是,线程对于 I/O 并发(网络、文件操作)很有用。join 等待线程完成。value 检索返回值。对于 CPU 密集型并行,使用多进程(fork、Sidekiq)或 JRuby(无 GIL)。始终 join 线程以避免主线程退出时它们被杀死。
# Create and run threads
threads = (1..3).map do |i|
Thread.new(i) do |n|
puts "Thread #{n} started"
sleep(1)
puts "Thread #{n} finished"
end
end
# Wait for all threads
threads.each(&:join)
puts "All done"
# Thread with return value
thread = Thread.new { 1 + 2 }
result = thread.value # 3 (waits for completion)
# Thread status
thread = Thread.new { sleep(1) }
puts thread.status # 'run', 'sleep', false (finished), nil (error)
thread.join
# Current thread
Thread.current
Thread.list # All threads
Thread.main # Main thread线程同步
Mutex.synchronize 确保一次只有一个线程执行块,防止竞争条件。Queue 是线程安全的 FIFO——生产者 push,消费者 pop(为空时阻塞)。ConditionVariable 协调线程:wait 释放互斥锁并休眠,signal/broadcast 唤醒等待线程。共享可变状态始终使用同步。当线程相互等待时发生死锁——以一致顺序获取锁。
# Mutex: mutual exclusion
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 (without mutex, would be less)
# Queue: thread-safe FIFO
require 'thread'
queue = Queue.new
producer = Thread.new do
5.times { |i| queue << "item #{i}" }
queue << :done
end
consumer = Thread.new do
loop do
item = queue.pop
break if item == :done
puts "Processed: #{item}"
end
end
[producer, consumer].each(&:join)
# ConditionVariable: signal between threads
mutex = Mutex.new
cv = ConditionVariable.newFiber
Fiber 是协作式轻量级线程:它们手动 yield 控制而非被抢占。resume 启动/恢复 fiber;Fiber.yield 暂停它并返回值。Fiber 适用于生成器、惰性求值和解析状态机。与线程不同,一次只有一个 fiber 运行,因此无需同步。Fiber 比线程便宜但不能使用多核。用于 I/O 多路复用(EventMachine、Async)。
# Fiber: cooperative concurrency (manual scheduling)
fiber = Fiber.new do
puts "Fiber started"
Fiber.yield "first yield"
puts "Fiber resumed"
Fiber.yield "second yield"
puts "Fiber ending"
"fiber done"
end
puts fiber.resume # "Fiber started", returns "first yield"
puts fiber.resume # "Fiber resumed", returns "second yield"
puts fiber.resume # "Fiber ending", returns "fiber done"
# Generator pattern with Fiber
def fibonacci
Fiber.new do
a, b = 0, 1
loop do
Fiber.yield a
a, b = b, a + b
end
end
end
fib = fibonacci
10.times { print fib.resume, " " }
# 0 1 1 2 3 5 8 13 21 34Async 与 Concurrent Ruby
async gem 在底层使用 Fiber 提供现代异步 I/O,实现高并发网络代码。concurrent-ruby 提供线程安全抽象:Future(异步结果)、Promise(可链式)、线程池和原子变量。I/O 密集型工作(HTTP、数据库)使用 async,CPU 密集型工作使用线程池。始终关闭池以避免挂起。这些 gem 绕过 MRI 的 GIL 实现实用并发。
# Async gem for modern async I/O
# gem install async
require 'async'
require 'async/http/internet'
Async do
internet = Async::HTTP::Internet.new
# Run requests concurrently
tasks = 3.times.map do |i|
Async do
response = internet.get("https://httpbin.org/delay/#{i}")
puts "Request #{i}: #{response.status}"
end
end
tasks.each(&:wait)
end
# Concurrent Ruby (gem 'concurrent-ruby')
require 'concurrent'
# Future: async computation
future = Concurrent::Future.execute { sleep(1); 42 }
puts future.value # 42 (blocks until ready)
# Promise chain
Concurrent::Promise.execute { 1 }
.then { |v| v + 1 }
.then { |v| v * 2 }
.then { |v| puts v } # 4
# Thread pool
pool = Concurrent::FixedThreadPool.new(4)
10.times do |i|
pool.post { puts "Task #{i} on #{Thread.current.object_id}" }
end
pool.shutdownRactor(Ruby 3.0+)
Ractor(Ruby 3.0+)通过避免 GIL 提供真正的并行。每个 Ractor 有自己的堆,因此无数据竞争。通信通过消息(send/receive、take)。发送的对象被复制(或用 Ractor.move 移动)以保持隔离。冻结对象可以共享。Ractor 是并行 Ruby 的未来,但有限制:大多数 gem 尚不是 Ractor 安全的。在隔离可接受的 CPU 密集型并行计算中使用它们。
# Ractor: true parallelism (no GIL)
# Ruby 3.0+ feature for thread-safe parallel execution
ractor = Ractor.new do
value = receive # Receive from main
value * 2
end
ractor.send(21)
puts ractor.take # 42
# Parallel computation
ractors = (1..4).map do |i|
Ractor.new(i) do |n|
sleep(1)
n * n
end
end
results = Ractor.select(*ractors)
# Or: ractors.map(&:take) # [1, 4, 9, 16]
# Ractor restrictions:
# - Cannot share mutable objects
# - Communication only via messages
# - Copy or move semantics for sending
# Shared frozen objects are OK
SHARED = Ractor.new { [1, 2, 3].freeze }
# Multiple ractors can read SHARED safely编码与 I/O
字符串编码
每个 Ruby 字符串都有编码(默认 UTF-8)。bytesize 是字节数;length 是字符数(多字节编码不同)。force_encoding 更改编码标签而不转换字节(当你知道实际编码时使用)。encode 在编码之间转换。处理外部数据前始终检查 valid_encoding?。用魔术注释 # encoding: utf-8 设置源文件编码(尽管 Ruby 2.0+ 中 UTF-8 是默认的)。
# Ruby strings have encodings
s = "hello"
puts s.encoding # <Encoding:UTF-8>
puts s.bytesize # 5
puts s.length # 5
# Multibyte characters
japanese = "こんにちは"
puts japanese.encoding # UTF-8
puts japanese.bytesize # 15 (3 bytes per char)
puts japanese.length # 5 (5 characters)
# Force encoding (interpret bytes differently)
bytes = "caf\u00e9".force_encoding('ASCII-8BIT')
puts bytes.encoding # ASCII-8BIT
# Encode (convert to different encoding)
utf8 = "café"
latin1 = utf8.encode('ISO-8859-1')
puts latin1.encoding # ISO-8859-1
# Invalid bytes
bad = "\xFF\xFE".force_encoding('UTF-8')
puts bad.valid_encoding? # false
fixed = bad.encode('UTF-8', invalid: :replace, replace: '?')文件 I/O
File.read 将整个文件加载到内存;File.foreach 逐行读取(对大文件节省内存)。File.write 覆盖;mode 'a' 追加;'r+' 读写。二进制模式('rb'、'wb')防止编码转换。始终使用块形式(File.open)确保文件关闭。FileUtils 提供更高级操作(cp、mv、mkdir_p)。读取前用 File.exist? 检查存在。
# Read entire file
content = File.read('data.txt')
# Read line by line (memory efficient)
File.foreach('large.log') do |line|
puts line.chomp
end
# Write to file
File.write('output.txt', 'Hello, World!')
# Append
File.open('log.txt', 'a') do |f|
f.puts "New log entry"
end
# Read/Write modes
File.open('file.txt', 'r+') do |f|
content = f.read
f.rewind
f.write("Updated: " + content)
end
# Binary mode
File.open('image.png', 'rb') do |f|
bytes = f.read
puts bytes.bytesize
end
# File operations
File.exist?('file.txt')
File.size('file.txt')
File.mtime('file.txt')
File.delete('file.txt')
File.rename('old.txt', 'new.txt')
FileUtils.cp('a.txt', 'b.txt') # require 'fileutils'StringIO 与 Tempfile
StringIO 用类似 IO 的接口包装字符串,适用于测试文件操作而不触及磁盘。Tempfile 创建在块退出时(Tempfile.create)或取消链接时(Tempfile.new)自动删除的临时文件。对于不适合内存的大数据或通过路径传递给外部程序,使用 Tempfile。始终用 ensure 块确保清理。StringIO 非常适合 IO 代码的单元测试。
require 'stringio'
require 'tempfile'
# StringIO: in-memory file-like object
io = StringIO.new
io.puts "Hello"
io.puts "World"
io.rewind
puts io.read # "Hello\nWorld\n"
# Use like a file
io = StringIO.new("line1\nline2\nline3")
io.each_line { |line| puts line.chomp }
# Tempfile: auto-deleted temporary file
Tempfile.create('myapp') do |f|
f.write('temporary data')
f.rewind
puts f.read
end # File automatically deleted
# Tempfile with explicit cleanup
temp = Tempfile.new('myapp')
begin
temp.write('data')
temp.rewind
# Use temp.path to pass to external programs
ensure
temp.close
temp.unlink
end网络 I/O
TCPSocket/TCPServer 提供低级 TCP 访问。Net::HTTP 是标准 HTTP 客户端(内置)。对于复杂 HTTP 需求(会话、cookie、重试),使用 httparty 或 faraday gem。HTTPS 始终设置 use_ssl = true。对于高性能 HTTP,考虑 async-http 或 typhoeus。URI 安全解析 URL。处理 Net::ReadTimeout 和 Errno::ECONNREFUSED 以获得健壮的网络代码。
require 'socket'
require 'net/http'
require 'uri'
# TCP Client
TCPSocket.open('example.com', 80) do |socket|
socket.write("GET / HTTP/1.0\r\n\r\n")
puts socket.read
end
# TCP Server
server = TCPServer.new(2000)
loop do
client = server.accept
client.puts "Hello from server"
client.close
end
# HTTP Client
uri = URI('https://api.example.com/data')
response = Net::HTTP.get_response(uri)
puts response.code # 200
puts response.body
# HTTP POST
response = Net::HTTP.post_form(uri, key: 'value')
# HTTP with custom headers
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer token'
response = http.request(request)STDIO 与管道
$stdout、$stderr、$stdin 是全局 IO 对象(使用常量 STDOUT/STDERR/STDIN 获取原始对象)。通过重新分配全局变量重定向。StringIO 捕获输出用于测试。Open3.popen3 完全控制子进程的 stdin/stdout/stderr。Open3.pipeline 像管道一样链式命令。Capture3 返回 stdout、stderr 和状态。始终关闭 stdin 以向子进程发出 EOF 信号。使用 wait.value 获取退出状态。
# Standard IO
$stdout.puts "To stdout"
$stderr.puts "To stderr"
input = $stdin.gets # Read from stdin
# Redirect IO
$stdout = File.open('log.txt', 'w')
puts "This goes to file"
$stdout = $stdout # Restore (or use STDOUT constant)
# Capture output
captured = StringIO.new
original = $stdout
$stdout = captured
puts "Captured"
$stdout = original
puts captured.string # "Captured\n"
# Open3: run commands with full IO control
require 'open3'
Open3.popen3('grep hello') do |stdin, stdout, stderr, wait|
stdin.puts "hello world"
stdin.puts "goodbye"
stdin.close
puts stdout.read # "hello world"
puts wait.value.exitstatus
end
# Pipeline
output = Open3.pipeline('ls', 'grep .rb', 'wc -l')模式匹配(3.0+)
基本模式匹配
模式匹配(in 关键字)解构数组和哈希,绑定变量。=> 将匹配值绑定到变量。*rest 捕获剩余数组元素。哈希匹配是部分的:额外键被忽略。| 匹配多个模式。case/in 是主要形式;单行形式是 expression in pattern。模式匹配在解析结构化数据(JSON、AST)和替换复杂 if-else 链时特别强大。
# in: pattern matching (Ruby 2.7+, stable in 3.0)
case [1, 2, 3]
in [1, *rest]
puts "Starts with 1, rest: #{rest}"
end
case {name: 'Alice', age: 30}
in {name: String => name, age: Integer => age}
puts "#{name} is #{age}"
end
# Multiple patterns with |
case status
in :success | :ok
puts "Good"
else
puts "Other"
end
# Array patterns
case [1, 2, 3, 4]
in [_, _, *rest]
puts "Rest: #{rest}" # [3, 4]
end
# Hash patterns (partial match by default)
case {a: 1, b: 2, c: 3}
in {a: Integer}
puts "Has a" # Matches (ignores b, c)
end变量绑定与守卫
=> 将匹配值绑定到变量。pin 运算符(^var)根据变量当前值匹配(而非绑定)。守卫(if/unless)添加条件。数组模式支持 * 在任意位置的 splat。查找模式 [*, target, *] 在数组中搜索元素。模式匹配对于复杂数据提取是声明式且简洁的。模式中绑定的变量在 case 语句之后可用。
user = {name: 'Alice', age: 30, role: :admin}
case user
in {name: String => name, age: Integer => age, role: :admin} if age > 18
puts "#{name} is an adult admin"
end
# Pin operator (^): match against variable value
expected = 'Alice'
case user
in {name: ^expected}
puts "Matched expected name"
end
# Array destructuring with binding
case [1, 2, 3]
in [first, *middle, last]
puts "First: #{first}, Middle: #{middle}, Last: #{last}"
end
# Find pattern (search within array)
case [1, 2, 3, 4, 5]
in [*, 3 => three, *]
puts "Found 3: #{three}"
end类与类型模式
类模式按类型匹配。自定义类通过实现 deconstruct(数组模式)和 deconstruct_keys(哈希模式)支持解构。模式 Class[args] 使用 deconstruct;Class(key:) 使用 deconstruct_keys。许多内置类(Time、Date、MatchData)支持解构。这实现了对领域对象的表达性匹配。实现这些方法使你的类可模式匹配。
# Match by class
case value
in Integer
puts "Integer"
in String
puts "String"
in Array
puts "Array"
end
# Class with destructuring
class Point
attr_reader :x, :y
def initialize(x, y) @x, @y = x, y end
def deconstruct = [@x, @y] # Array pattern
def deconstruct_keys(keys) = {x: @x, y: @y} # Hash pattern
end
p = Point.new(1, 2)
case p
in Point[x, y]
puts "Point at (#{x}, #{y})"
end
case p
in Point(x:, y:)
puts "X: #{x}, Y: #{y}"
end
# Built-in classes support destructuring
case Time.now
in Time(hour: h) if h < 12
puts "Morning"
end单行模式匹配
单行形式(expr in pattern)返回 true/false 而非引发 NoMatchingPatternError。它在成功匹配时绑定变量。适用于守卫子句和条件提取。右向形式(pattern => var)绑定整个匹配。单行匹配对简单情况很简洁;复杂多分支逻辑使用 case/in。失败匹配中绑定的变量保持 nil。
# One-line form (Ruby 3.0+)
{status: :ok, data: 42} in {status: :ok, data: Integer => data}
puts data # 42
# Returns true/false (does not raise)
[1, 2, 3] in [Integer, Integer, Integer] # true
[1, 2, 3] in [String, *, *] # false
# Useful for conditionals
if user in {role: :admin}
puts "Admin access"
end
# Guard clauses
def process(data)
return unless data in {type: String, value: Integer}
# ...
end
# Extracting from JSON
json = JSON.parse('{"user":{"name":"Alice","age":30}}')
json in {"user" => {"name" => String => name, "age" => Integer => age}}
puts "#{name}, #{age}" # Alice, 30实际用例
模式匹配擅长:解析结构化数据(JSON、XML)、状态机、AST 遍历和按数据形状派发。它用声明式模式替换冗长的 if-else 链。else 子句处理意外格式。与守卫结合用于复杂条件。模式匹配使复杂数据处理的代码更可读、更可维护。它是 Ruby 3 最强大的功能之一,用于干净、富有表现力的代码。
# 1. Parse JSON API responses
def handle_response(response)
case JSON.parse(response)
in {"status" => "success", "data" => Array => items}
items.each { |item| process(item) }
in {"status" => "error", "message" => String => msg}
raise "API error: #{msg}"
in {"status" => "error", "code" => Integer => code} if code >= 500
retry_request
else
raise "Unknown response format"
end
end
# 2. State machines
case state
in :idle, event: :start
transition_to :running
in :running, event: :pause
transition_to :paused
in :paused, event: :resume
transition_to :running
in :running | :paused, event: :stop
transition_to :idle
end
# 3. AST traversal
def evaluate(node)
case node
in {type: 'number', value: n}
n
in {type: 'add', left:, right:}
evaluate(left) + evaluate(right)
in {type: 'mul', left:, right:}
evaluate(left) * evaluate(right)
end
end元编程
send 与 define_method
define_method 动态创建方法。send 按名称调用方法(甚至私有)。public_send 尊重可见性。适用于 DSL 和减少样板。小心用户输入以避免安全问题。元编程强大但会使代码更难理解。
class Foo
define_method(:greet) do |name|
"Hello, #{name}"
end
end
Foo.new.greet("Alice") # "Hello, Alice"
# Dynamic method call
Foo.new.send(:greet, "Bob") # "Hello, Bob"
Foo.new.public_send(:greet, "Bob") # Respects visibilitymethod_missing
method_missing 拦截对未定义方法的调用。适用于动态派发和 DSL。始终重写 respond_to_missing? 以匹配。可能很慢并隐藏 bug。方法集合已知时优先使用 define_method。ActiveRecord 用于属性访问器。
class DynamicHash
def method_missing(name, *args)
if name.to_s.end_with?('=')
self[name.to_s.chomp('=')] = args.first
else
self[name.to_s]
end
end
def respond_to_missing?(name, include_private = false)
true
end
endeval
eval 将字符串作为 Ruby 代码执行。极其强大但危险。切勿 eval 不受信任的输入(代码注入)。使用 binding.eval 获得特定上下文。对于安全求值,使用 Ripper 等解析器或沙箱。大多数用例有更安全的替代方案。
# Execute a string as Ruby code
result = eval("1 + 2 * 3") # 7
# Dynamic method definition
eval("def dynamic_method; 42; end")
dynamic_method # 42
# WARNING: never eval untrusted input!开放类
Ruby 类是开放的:你可以向任何类添加方法,包括内置类。这称为猴子补丁。对于快速修复很强大但可能导致冲突和混淆。使用细化进行范围修改。清楚地记录更改。优先使用组合而非猴子补丁。
class String
def shout
upcase + "!"
end
end
"hello".shout # "HELLO!"
# Monkey patching: modifying existing classes
# Use sparingly, can cause conflicts类宏
类宏是定义其他方法的类方法。has_many、belongs_to、attr_accessor 是示例。它们内部使用 define_method。这是 Rails 创建动态方法的方式。RSpec 和 Sinatra 等 DSL 使用此模式。使代码声明式且可读。
class ActiveRecord::Base
def self.has_many(name)
define_method(name) { [] }
end
end
class Post < ActiveRecord::Base
has_many :comments
end
Post.new.comments # []相关 Ruby 代码片段
Copy-paste ready code for common tasks.
Blocks, Procs, Lambdas
Use blocks with yield, Procs, lambdas, and the & operator.
Classes and Modules
Define classes with inheritance, mix in modules, and add class methods.
Iterators
Use each, map, select, reduce, group_by, and lazy enumerators.
Strings
Interpolate, trim, split, replace, and pattern-match strings.
Hashes
Build, default, transform, merge, and group with Hash.
Metaprogramming
Define methods dynamically, intercept with method_missing, and build DSLs.
Error Handling
Raise and rescue typed exceptions with ensure and retry.
File I/O
Read, write, append, traverse directories, and process CSV files.
这篇内容对您有帮助吗?