Ruby 语法速查表 - Ruby 常用方法与惯用法大全

这张速查给刚开始写 Ruby(包括 Rails)或已写了一阵、想快速回忆惯用法的开发者。内容从变量、符号与常见内置方法开始,覆盖数组/哈希、控制流(含后置 if 和 unless)、方法与块/Proc/Lambda、类与模块(attr_accessor、include/extend、继承),最后是 begin-rescue-ensure 异常处理。与多数语言不同,Ruby 里几乎一切皆对象、方法默认返回最后一个表达式的值,块能优雅地封装遍历逻辑。读完你应能写出惯用且简洁的 Ruby 代码:用迭代器替代手写循环、用符号做哈希键、用模块组织可复用行为。

编程语言·共 69 条命令·最后更新 2026-07-21
rubyrailsruby on rails编程语言

典型使用场景

面向用 Ruby 写脚本、处理数据或维护 Rails 项目的开发者。本表重点覆盖语言层最容易混淆的部分:符号 :symbol 与字符串的本质差别、块的三种传递与调用方式(yield / Proc.new / lambda)、attr_accessor 一行生成读写方法、以及 map/select/reject/partition/group_by 这条完整的集合处理链。日常任务里最常见的是「遍历数组做过滤再分组统计」,用 select + map + group_by + reduce 组合即可;遇到循环跳不出、变量意外为 nil、方法名冲突时,可直接翻易错点与排障段定位。类与混入、异常处理与 catch/throw 则对应更长期的代码组织需求。

基础语法 Basics 7

name = "Ruby"
变量赋值,动态类型无需声明
name = "Hello, #{name}"
字符串插值,双引号有效
:symbol
符号,不可变且唯一,适合做哈希键
# 这是单行注释
单行注释以 # 开头
=begin 注释块 =end
多行注释块
nil
空值,nil 也是对象
true / false
布尔值,也是对象

数组与哈希 Array & Hash 9

arr = [1, 2, 3]
用字面量 [] 创建数组,可混合放入任意类型元素
arr << 4
追加元素
arr[0]
访问索引 0 的元素
arr.first / arr.last
第一个 / 最后一个元素
hash = { a: 1, b: 2 }
创建哈希(符号键简写语法)
hash[:a]
访问哈希键值
hash[:c] = 3
设置哈希键值
hash.key?(:a)
检查键是否存在
hash.keys / hash.values
获取所有键 / 值

控制流 Control Flow 10

if x > 0 puts "positive" end
if 条件语句
puts "positive" if x > 0
后置 if,简洁写法
unless x > 0 puts "not positive" end
unless 条件,相当于 if !
case x when 1 then "one" else "other" end
case 多分支匹配
3.times { puts "hi" }
重复执行指定次数
1.upto(5) { |n| puts n }
从 1 递增到 5
5.downto(1) { |n| puts n }
从 5 递减到 1
loop { break if done }
无限循环,需手动 break
while x > 0 x -= 1 end
while 循环
until x == 0 x -= 1 end
until 循环,相当于 while !

方法定义 Methods 9

def greet(name) "Hello, #{name}" end
定义方法,最后一行自动返回
def greet(name = "World")
方法参数带默认值
def sum(*args)
splat 运算符,接收可变参数
def method(a:, b:)
关键字参数,顺序无关
return value
显式返回值(可选)
def method yield if block_given? end
yield 调用传入的块
my_proc = Proc.new { |x| x*2 }
创建 Proc 对象
my_lambda = ->(x) { x*2 }
创建 Lambda(箭头语法)
proc.call(5)
调用 Proc / Lambda

类与模块 Class & Module 10

class MyClass end
定义类,类名首字母大写是约定
attr_accessor :name
为 :name 自动生成读取与赋值两个方法
attr_reader :name
只读 getter
attr_writer :name
只写 setter
class Child < Parent
类继承
module MyModule end
定义模块
include MyModule
混入模块方法(实例方法)
extend MyModule
混入模块方法(类方法)
self.class_method
self 表示当前对象,定义类方法
class << self end
单例类,定义类方法

异常处理 Exception 7

begin risky_call rescue "error" end
begin-rescue 捕获异常
rescue SpecificError => e
捕获指定类型的异常
ensure cleanup end
ensure 保证执行清理代码
raise "error message"
手动抛出异常
raise ArgumentError, "msg"
抛出指定类型的异常
retry
在 rescue 中重试 begin 块
catch(:done) { throw(:done) }
catch/throw 非局部跳转

常用内置方法 Built-in Methods 10

arr.map { |x| x*2 }
Array#map 转换数组
arr.select { |x| x.even? }
Array#select 筛选偶数
arr.reject { |x| x.even? }
Array#reject 排除偶数
arr.reduce(0) { |s, x| s + x }
Array#reduce 归约求和
hash.merge({ c: 3 })
Hash#merge 合并哈希
"hello".upcase
String#upcase 大写
"hello".split("")
String#split 分割字符串
"hello".gsub(/l/, "L")
String#gsub 全局替换
arr.flatten
Array#flatten 展开嵌套数组
arr.uniq
Array#uniq 去重

块与迭代器 Blocks & Iterators 7

[1, 2].each { |n| puts n }
each 遍历每个元素
[1, 2].each do |n| puts n end
do...end 块,等价于 {}
(1..5).map { |n| n**2 }
map 转换(返回新数组)
[1, 2, 3].select(&:even?)
select 带 &: 简写语法
arr.partition { |x| x.even? }
partition 分为两组 [[偶数], [奇数]]
arr.chunk { |x| x > 0 }
chunk 按条件分组
arr.group_by { |x| x % 3 }
group_by 按条件分组为哈希

参数矩阵

参数作用示例
name = "Ruby"动态类型变量赋值,无需声明类型name = "Ruby"
"Hello, #{name}"双引号内字符串插值puts "Hello, #{name}"
:symbol不可变且唯一的符号,适合做哈希键hash = { a: 1, b: 2 }
arr << 4向数组末尾追加元素arr = [1, 2, 3]; arr << 4
arr.first / arr.last取首元素 / 末元素arr.first
hash.key?(:a)判断键是否存在hash.key?(:a)
hash.keys / hash.values取出全部键 / 值hash.keys
puts "positive" if x > 0后置 if 条件修饰写法puts "positive" if x > 0
unless x > 0 ... end条件不成立时执行,相当于 if !unless x > 0; puts "not positive"; end
case x when 1 then ... else ... end多分支匹配case x; when 1 then "one"; else "other"; end
3.times { ... }按次数重复执行3.times { puts "hi" }
def greet(name = "World")定义带默认参数的方法def greet(name = "World"); "Hello, #{name}"; end
def sum(*args)splat 接收可变数量参数def sum(*args); args.sum; end
def method(a:, b:)关键字参数,调用顺序无关def config(a:, b:); end
def method; yield if block_given?; end在方法内调用传入的块def each_x; yield if block_given?; end
Proc.new { |x| x*2 } / ->(x) { x*2 }创建 Proc 对象 / Lambda(箭头语法)my_lambda = ->(x) { x * 2 }; my_lambda.call(5)
attr_accessor :name一次生成 getter 与 setterattr_accessor :name
include MyModule / extend MyModule混入实例方法 / 混入类方法include MyModule
arr.map { |x| x*2 }转换数组,返回新数组arr.map { |x| x * 2 }
arr.select / arr.reject筛选保留 / 排除符合条件的元素arr.select { |x| x.even? }
arr.reduce(0) { |s, x| s + x }归约累积求和arr.reduce(0) { |s, x| s + x }
arr.group_by { |x| x % 3 }按键值分组,返回哈希arr.group_by { |x| x % 3 }
arr.partition { |x| x.even? }一次拆成满足与不满足两组arr.partition { |x| x.even? }
rescue SpecificError => e捕获指定类型异常并绑定变量begin; risky_call; rescue SpecificError => e; end
ensure ... end无论成败都执行清理代码begin; risky_call; ensure; cleanup; end
raise ArgumentError, "msg"抛出指定类型异常raise ArgumentError, "msg"
catch(:done) { throw(:done) }非局部跳转,可跳出深层嵌套循环catch(:done) { throw(:done) }

易错点与避坑指南

现象哈希用字符串键查不到值,输出恒为 nil,明明刚写进去过

原因符号 :a 和字符串 "a" 是两种不同的对象,哈希按键的类型与相等性严格匹配,键不会被自动转换。

处置统一约定使用符号键(hash[:a]),或在读取端显式写 hash[sym.to_s] / hash[key.to_sym] 完成转换。

现象用 map 想「原地」改数组,结果原数组没变,脚本后续还用旧数据

原因map、select、reject 都返回新数组,不修改原对象;只有 <<、[]= 之类的写法会改动原数组。

处置显式接住返回值:arr = arr.map { |x| x * 2 };或改用 select! / map! 这类带叹号的原地版本。

现象循环里用 break 后外层代码被跳过一大段,逻辑像随机执行

原因块中的 break 会终止整个方法调用所在的循环(跳出方法),而不是只跳出块自身。

处置需要只跳出迭代时用 next(跳过本次);需要跳出多层嵌套用 catch(:done) { throw(:done) } 配对使用。

现象方法执行到中途没有 return,外部拿到的却是块内最后一次表达式的中间值

原因Ruby 方法返回最后一条表达式的值;块内的赋值或遍历会改变「最后一句」的含义,容易误判返回值。

处置把真正的返回值写在方法最后一行,或显式写 return value。

现象定义了带 yield 的方法,但直接当普通方法调用时报 LocalJumpError 之类的问题

原因yield 只有在调用方传入块时才有效,没有块就等于没有可执行的块体。

处置用 block_given? 判断后再 yield,或给调用处补上 { ... } / do ... end 块。

现象begin...rescue 没拦住错误,异常照样冒到顶层终止脚本

原因rescue 必须紧跟在其保护的 begin 块中;把 rescue 写在与 begin 不同的层级会形成两块不同作用域,异常不会被捕获。

处置把可能抛错的调用完整包进 begin ... rescue => e ... ensure ... end 结构里,检查缩进层级是否正确。

现象给类加了一堆方法后,同名实例方法被覆盖,行为悄悄变了

原因include 混入的模块方法会进入祖先链,优先级高于类自身定义,容易覆盖同名逻辑。

处置明确职责边界:通用能力放模块用 include,只服务于该类自身实例的逻辑直接定义在类里;类方法用 extend 单独混入。

现象调试时把 {} 写很长一行,块与相邻方法调用黏在一起难以断点

原因{} 绑定优先级较高,长表达式中容易和后续代码混淆;do...end 与 {} 在部分场景绑定规则不同。

处置多行块一律改用 do ... end,单行才用 {};配合 Alt+Enter(编辑器)等快速修复能力定位。

排障路径

  1. 1确认哈希键类型与内容

    hash.keys; hash.key?(:a); hash[:a]; hash["a"]

    keys 输出能看出键究竟是符号还是字符串;同一份数据分别用 :a 和 "a" 取值,只有匹配类型的那次有值,另一次第返回 nil,据此即可定位符号/字符串不匹配问题。

  2. 2查清数组里到底装了什么

    arr = [1, 2, 3]; arr.map { |x| x * 2 }; arr; arr.select { |x| x.even? }; arr.uniq

    map 的返回值是新数组,arr 本身不变;select 返回筛选后的新数组;uniq 去重。想确认修改是否生效,请看打印出的 arr 本身而不是链式中间结果。

  3. 3区分一次拆分与多次筛选

    arr = [1,2,3,4,5]; even, odd = arr.partition { |x| x.even? }; even; odd

    partition 一次遍历把元素分成两组并返回两个数组,多重赋值可直接解包。若结果不符合预期,说明判断条件 x.even? 与元素类型不匹配(例如元素本身是字符串)。

  4. 4按条件分组统计

    arr.group_by { |x| x % 3 }; arr.group_by { |x| x % 3 }.map { |k, v| [k, v.size] }

    group_by 返回「键 => 元素数组」的哈希;再对每个值取 size 就得到各组数量,能快速确认分组逻辑是否符合预期。

  5. 5确认块是否真的被执行

    def each_x; yield if block_given?; "done"; end; each_x { |v| puts v }

    如果没有任何输出,说明块_given? 判断失败或块未传入;"done" 仍被返回说明方法体走到了末尾,可以据此区分「块没执行」与「方法没被调用」。

  6. 6规约求和并处理空数组

    arr.reduce(0) { |s, x| s + x }; [].reduce(0) { |s, x| s + x }

    给出初始值 0 时空数组也能安全返回 0;若省略初始值直接 arr.reduce { |s, x| s + x },空数组会因无初值而报错,脚本里建议始终显式传初值。

命令示例

链式枚举把数据清洗写清楚

arr = [1, 2, 3, 4, 5, 6] arr.select(&:even?).map { |x| x * 10 }.reduce(0) { |s, x| s + x }

select/map/reduce 各自返回新集合、不改原数组,可以放心串起来;&:even? 是把方法名变成 symbol 传给块的简写,少一次 |x| 样板。

输出

=> 120

按条件分组看分布

arr = [1, 2, 3, 4, 5] arr.group_by { |x| x % 3 } arr.partition { |x| x > 3 }

group_by 返回哈希便于按桶统计;partition 返回两个数组,第一个元素是通过的那组,顺序固定但容易记反。

输出

{0=>[3], 1=>[1, 4], 2=>[2, 5]}
[[4, 5], [1, 2, 3]]

字符串替换与切分

log = "2024-05-01 ERROR timeout" log.gsub(/\d{4}-\d{2}-\d{2}/, "DATE") log.split(" ")

gsub 里传正则才能处理日期这类模式,传字符串只做字面替换。gsub 返回新字符串,原变量不变,不需要 destructive_ 前缀那套写法。

输出

=> "DATE ERROR timeout"
=> ["2024-05-01", "ERROR", "timeout"]

关键字参数让调用处自解释

def connect(host:, port: 443, timeout: 30) puts "#{host}:#{port} in #{timeout}s" end connect(host: "db.internal")

关键字参数顺序无关、忘传会直接 ArgumentError 而不是错位到别的参数上,调用 connect("a", "b") 这种位置参数的笔误因此被挡在编译期之外。

输出

=> db.internal:443 in 30s

类骨架与 attr_accessor

class User attr_accessor :name def initialize(name) @name = name end end User.new("Tom").name

attr_accessor 一行生成读取与赋值两个方法;用 attr_reader 则外部只能读。实例变量 @name 必须在 initialize 里赋值,否则读取时得到 nil 而不报错。

输出

=> "Tom"

常见坑与注意事项

  • { } 块比 do...end 绑定得更紧:array.each { puts method } 里的 method 会被当成块参数传入并 ArgumentError,必须改用 do...end。
  • 数组索引从 0 开始,但 arr[-1] 取的是最后一个元素而不是倒数第一之前的语义直觉值(它是倒数第一个);off-by-one 在切片时会连带出错。
  • 方法体最后一个表达式的值会自动成为返回值,中途写了一句 puts 或日志语句就把返回值改掉了,返回 nil 时调用方很难看出原因。
  • rescue 不带类型会捕获 StandardError 之外的大量异常,写成裸 rescue 还可能把 Ctrl+C 的 Interrupt 一起吞掉,使脚本无法中断。
  • proc.call 与 lambda.call 的参数检查不同:lambda 参数个数必须严格匹配,少传一个就 ArgumentError;把 lambda 当 proc 用会省掉这层保护。
  • 字符串在 Ruby 中是可变的,str << "x" 会原地修改。若这个字符串来自 hash 的键或常量,直接改会连键一起污染,报 key not found。

提示

  • Ruby 中一切皆对象,包括 nil 和数字
  • each 是最常用的迭代器,map 用于转换,select 用于筛选
  • 符号 :symbol 不可变且唯一,适合做哈希键
  • attr_accessor 自动生成 getter 和 setter
  • 块(block)是 Ruby 最强大的特性,do...end 和 {} 等价

常见问题

map、select、reject 该挑哪一个,差别体现在返回值上吗?

三者的差别正是返回值:map 逐元素转换并返回等长的新数组;select 只返回块为真的元素,长度会变;reject 返回块为假的元素,常与 select 配对使用,例如 arr.partition { |x| x.even? } 一次就拿到 [偶数, 奇数] 两组。只求遍历副作用时用 each,它的返回值是原数组本身,误用 map 只会白做一份拷贝。

符号 :symbol 和字符串 "symbol" 用在哈希键上该怎么选?

符号不可变且在程序运行期只有一份实例,适合做哈希键和 attr_accessor :name 这类方法名,查找快、内存省;字符串可变,每次字面量求值都可能新建对象,用作键时哈希还要额外计算字符串内容哈希。约定是:标识符一律用符号 :a,需要拼接、来自用户输入或要与外部 JSON 字符串互换时才用字符串,注意 hash.key?(:a) 与 hash.key?("a") 在 Ruby 里互不命中。

Proc 和 Lambda 用起来有什么区别,选错了会有什么后果?

两者都用块语法创建(Proc.new { |x| x*2 } 与 ->(x) { x*2 }),差别在调用检查:Lambda 是严格的,参数个数必须与定义一致,return 会从定义它的方法返回;Proc 是宽松的,参数可多可少,块内的 return 会引发 LocalJumpError。回调若由外部代码决定传几个参数,或依赖 return 语义,用 Lambda 更安全;不确定时优先 -> 箭头语法。

怎么给类批量生成读写访问器,为什么不手写两个方法?

用 attr_accessor :name 一次性生成 :name 的读取与赋值方法,attr_reader :name 只生成 getter(字段对外只读时用,避免外部绕过校验直接改状态),attr_writer :name 只生成 setter。手写重复的 get/set 既冗长又容易在改名时漏改。定义在类体里的是实例方法;要让模块的方法变成类方法,改用 extend MyModule,而 include 只混入实例方法。

循环里想提前退出,break、next 和 catch/throw 怎么选?

break 立即跳出最内层的 loop 或 3.times { } 块;next 相当于 continue,跳过本次迭代继续下一轮。两者都只影响一层循环,嵌套结构里想从最深处终止外层循环就不够用了——这时用非局部跳转:catch(:done) { throw(:done) },配合 break 或 return 从任意深度退出单个块,是 Ruby 处理生成器式提前终止的惯用手段。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们