1 Bundler 深入
Bundler 是 Ruby 的依赖管理工具,通过 Gemfile 声明项目依赖,通过 Gemfile.lock 锁定精确版本,确保团队和部署环境的一致性。
Gemfile 语法
source 'https://rubygems.org'
ruby '~> 3.3.0'
gem 'rails', '~> 7.1'
gem 'pg', '>= 1.5'
gem 'puma', '~> 6.0'
gem 'sidekiq'
gem 'faraday', '~> 2.0'
gem 'nokogiri', require: false
gem 'my_gem', path: '../my_gem'
gem 'private_gem', git: 'https://github.com/org/private_gem.git', branch: 'main'
group :development do
gem 'rubocop', require: false
gem 'solargraph'
gem 'debug'
end
group :test do
gem 'rspec', '~> 3.12'
gem 'factory_bot'
gem 'faker'
gem 'simplecov', require: false
end
group :development, :test do
gem 'dotenv'
gem 'pry'
end
group :production do
gem 'redis', '~> 5.0'
end
版本约束语法
| 写法 | 含义 | 匹配范围 |
|---|---|---|
| '~> 2.1' | 悲观约束(推荐) | >= 2.1, < 3.0 |
| '~> 2.1.3' | 悲观约束(补丁级) | >= 2.1.3, < 2.2.0 |
| '>= 1.0' | 大于等于 | 1.0 及以上所有版本 |
| '2.1.0' | 精确版本 | 仅 2.1.0 |
| (无版本) | 任意版本 | 最新兼容版本 |
常用命令
# 初始化
bundle init
# 安装依赖(首次或 Gemfile 变更后)
bundle install
# 更新特定 gem
bundle update faraday
# 更新所有 gem(谨慎使用)
bundle update
# 在 bundle 环境中执行命令
bundle exec rspec
bundle exec rake db:migrate
# 查看过期的 gem
bundle outdated
# 查看 gem 安装位置
bundle show faraday
# 添加 gem(自动写入 Gemfile)
bundle add sidekiq
# 移除 gem
bundle remove sidekiq
# 检查安全漏洞
bundle audit
⚡ Gemfile.lock 的重要性
- 必须提交到 Git(应用项目)——确保团队和 CI/CD 使用完全相同的版本
- Gem 库项目则不提交 Gemfile.lock——让使用者自行解析依赖
bundle install优先使用 lock 文件中的版本,bundle update才会更新
2 RSpec 测试框架
RSpec 是 Ruby 社区最流行的测试框架,采用 BDD(行为驱动开发)风格,测试代码读起来像自然语言描述。
gem install rspec
rspec --init # 生成 .rspec 和 spec/spec_helper.rb
基础结构
# spec/calculator_spec.rb
require_relative '../calculator'
RSpec.describe Calculator do
describe '#add' do
it '返回两个数的和' do
calc = Calculator.new
expect(calc.add(2, 3)).to eq(5)
end
it '处理负数' do
calc = Calculator.new
expect(calc.add(-1, -2)).to eq(-3)
end
it '处理浮点数' do
calc = Calculator.new
expect(calc.add(0.1, 0.2)).to be_within(0.001).of(0.3)
end
end
describe '#divide' do
it '返回两个数的商' do
calc = Calculator.new
expect(calc.divide(10, 3)).to eq(3)
end
context '当除数为零时' do
it '抛出 ZeroDivisionError' do
calc = Calculator.new
expect { calc.divide(10, 0) }.to raise_error(ZeroDivisionError)
end
end
end
end
常用 Matchers
expect(value).to eq(expected)
expect(value).to eql(expected)
expect(value).to be(expected)
expect(value).to equal(expected)
expect(value).to be > 5
expect(value).to be_between(1, 10).inclusive
expect(value).to be_within(0.1).of(3.14)
expect(string).to include('hello')
expect(string).to start_with('Hello')
expect(string).to end_with('world')
expect(string).to match(/\d{3}-\d{4}/)
expect(array).to include(1, 3)
expect(array).to contain_exactly(3, 1, 2)
expect(array).to have_attributes(size: 3)
expect(hash).to include(name: '张三')
expect(value).to be_nil
expect(value).to be_truthy
expect(value).to be_falsey
expect(value).to be_a(String)
expect(value).to respond_to(:length)
expect { dangerous_operation }.to raise_error(RuntimeError, /失败/)
expect { array.push(1) }.to change { array.size }.by(1)
expect { puts 'hello' }.to output(/hello/).to_stdout
before/after 与 let
RSpec.describe User do
let(:user) { User.new(name: '张三', email: 'zhangsan@example.com') }
let!(:admin) { User.create!(name: 'Admin', role: :admin) }
before(:each) do
DatabaseCleaner.start
end
after(:each) do
DatabaseCleaner.clean
end
before(:all) do
@shared_resource = ExpensiveSetup.new
end
describe '#valid?' do
it '有效用户返回 true' do
expect(user).to be_valid
end
context '缺少姓名时' do
let(:user) { User.new(name: '', email: 'test@example.com') }
it '返回 false' do
expect(user).not_to be_valid
expect(user.errors[:name]).to include("不能为空")
end
end
end
end
Mocking 与 Stubbing
RSpec.describe OrderService do
describe '#place_order' do
it '发送通知邮件' do
mailer = instance_double(OrderMailer)
allow(OrderMailer).to receive(:new).and_return(mailer)
expect(mailer).to receive(:send_confirmation).with(order_id: 123)
service = OrderService.new
service.place_order(order_id: 123)
end
it '调用支付网关' do
gateway = double('PaymentGateway')
allow(gateway).to receive(:charge).and_return({ status: 'success', id: 'txn_123' })
service = OrderService.new(payment_gateway: gateway)
result = service.place_order(amount: 99.99)
expect(result[:status]).to eq('success')
expect(gateway).to have_received(:charge).with(amount: 99.99)
end
end
end
# 运行测试
bundle exec rspec
# 运行特定文件
bundle exec rspec spec/models/user_spec.rb
# 运行特定行
bundle exec rspec spec/models/user_spec.rb:15
# 只运行失败的测试
bundle exec rspec --only-failures
# 格式化输出
bundle exec rspec --format documentation
3 Rake 构建工具
Rake 是 Ruby 版的 Make——用 Ruby 语法定义任务和依赖关系。Rails 项目的 rake db:migrate、rake routes 等命令都基于 Rake。
Rakefile 基础
# Rakefile
desc '打招呼'
task :hello do
puts 'Hello from Rake!'
end
desc '显示当前时间'
task :time do
puts Time.now
end
task default: :hello
rake hello # 运行 hello 任务
rake time # 运行 time 任务
rake # 运行默认任务(hello)
rake -T # 列出所有任务
任务依赖
desc '准备环境'
task :setup do
puts '安装依赖...'
sh 'bundle install'
end
desc '运行测试'
task test: :setup do
sh 'bundle exec rspec'
end
desc '代码检查'
task lint: :setup do
sh 'bundle exec rubocop'
end
desc '完整 CI 流程'
task ci: [:lint, :test] do
puts '✅ CI 通过!'
end
命名空间
namespace :db do
desc '创建数据库'
task :create do
puts '创建数据库...'
end
desc '运行迁移'
task migrate: :create do
puts '运行数据库迁移...'
end
desc '填充种子数据'
task seed: :migrate do
puts '填充种子数据...'
end
desc '重置数据库(删除 + 创建 + 迁移 + 种子)'
task reset: [:create, :migrate, :seed]
end
namespace :assets do
desc '编译资源文件'
task :compile do
puts '编译 CSS/JS...'
end
desc '清理编译产物'
task :clean do
rm_rf 'public/assets'
puts '已清理'
end
end
rake db:migrate
rake db:reset
rake assets:compile
带参数的任务
desc '部署到指定环境'
task :deploy, [:env] do |t, args|
env = args[:env] || 'staging'
puts "部署到 #{env} 环境..."
sh "cap #{env} deploy"
end
namespace :user do
desc '创建管理员'
task :create_admin, [:name, :email] do |t, args|
puts "创建管理员: #{args[:name]} (#{args[:email]})"
end
end
rake deploy[production]
rake user:create_admin[张三,zhangsan@example.com]
🔄 构建工具对比
| 工具 | 语言 | 配置文件 | 特点 |
|---|---|---|---|
| Rake | Ruby | Rakefile | 纯 Ruby 语法,表达力强 |
| Make | C/通用 | Makefile | 经典工具,Tab 缩进要求 |
| Gradle | Java/Kotlin | build.gradle | Groovy/Kotlin DSL,功能强大 |
| npm scripts | Node.js | package.json | JSON 配置,Shell 命令 |
4 热门 Gem 推荐
🔍 Nokogiri — HTML/XML 解析
require 'nokogiri'
require 'open-uri'
doc = Nokogiri::HTML(URI.open('https://example.com'))
doc.css('h1').each { |h1| puts h1.text }
links = doc.css('a[href]').map { |a| a['href'] }
items = doc.xpath('//div[@class="item"]')
items.each do |item|
title = item.at_css('.title')&.text
price = item.at_css('.price')&.text
puts "#{title}: #{price}"
end
🚀 Puma — 高性能 Web 服务器
# config/puma.rb
workers ENV.fetch('WEB_CONCURRENCY', 2)
threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
threads threads_count, threads_count
port ENV.fetch('PORT', 3000)
environment ENV.fetch('RACK_ENV', 'development')
preload_app!
on_worker_boot do
ActiveRecord::Base.establish_connection
end
# 启动
bundle exec puma -C config/puma.rb
⚡ Sidekiq — 后台任务
class ReportWorker
include Sidekiq::Job
sidekiq_options queue: 'default', retry: 3
def perform(user_id, report_type)
user = User.find(user_id)
report = ReportGenerator.new(user)
report.generate(report_type)
ReportMailer.send(user, report)
end
end
ReportWorker.perform_async(42, 'monthly')
ReportWorker.perform_in(1.hour, 42, 'daily')
🛤️ Rails — 全栈框架概览
# 创建新项目
rails new myapp --database=postgresql
# 生成资源
rails generate scaffold Post title:string body:text
# 数据库操作
rails db:create db:migrate db:seed
# 启动服务器
rails server
# 控制台
rails console
💧 Dry-rb — 函数式工具集
require 'dry/validation'
class UserContract < Dry::Validation::Contract
params do
required(:name).filled(:string, min_size?: 2)
required(:email).filled(:string, format?: /@/)
required(:age).filled(:integer, gteq?: 18)
end
rule(:email) do
key.failure('已被注册') if User.exists?(email: value)
end
end
result = UserContract.new.call(
name: '张三', email: 'zhangsan@example.com', age: 28
)
puts result.success?
puts result.errors.to_h
📡 HTTParty — 简易 HTTP
require 'httparty'
response = HTTParty.get('https://api.example.com/users')
puts response.parsed_response
response = HTTParty.post(
'https://api.example.com/users',
body: { name: '张三', email: 'z@example.com' }.to_json,
headers: { 'Content-Type' => 'application/json' }
)
class GitHub
include HTTParty
base_uri 'https://api.github.com'
headers 'User-Agent' => 'Ruby HTTParty'
def self.user(username)
get("/users/#{username}")
end
end
puts GitHub.user('matz')['name']
5 Ruby 代码规范
RuboCop 是 Ruby 社区的标准代码检查和格式化工具,基于 Ruby Style Guide 实施代码规范。
gem install rubocop
# 检查当前目录
rubocop
# 自动修复可安全修复的问题
rubocop -a
# 激进自动修复(可能改变行为,需 review)
rubocop -A
# 只检查特定文件
rubocop app/models/user.rb
# 生成配置文件
rubocop --init
.rubocop.yml 配置
# .rubocop.yml
AllCops:
TargetRubyVersion: 3.3
NewCops: enable
Exclude:
- 'vendor/**/*'
- 'db/schema.rb'
- 'node_modules/**/*'
Style/StringLiterals:
EnforcedStyle: single_quotes
Style/Documentation:
Enabled: false
Metrics/MethodLength:
Max: 20
Metrics/BlockLength:
Exclude:
- 'spec/**/*'
- 'Rakefile'
- '*.gemspec'
Layout/LineLength:
Max: 120
Style/FrozenStringLiteralComment:
EnforcedStyle: always
💡 Ruby 代码风格要点
- 缩进使用 2 空格(非 Tab),这是 Ruby 社区共识
- 方法名和变量名用
snake_case,类名用CamelCase - 谓词方法以
?结尾(如empty?),危险方法以!结尾(如save!) - 字符串默认使用单引号,需要插值时用双引号
6 调试工具
debug gem(Ruby 3.1+ 内置)
require 'debug'
def calculate_total(items)
subtotal = items.sum { |item| item[:price] * item[:quantity] }
binding.break
tax = subtotal * 0.1
subtotal + tax
end
items = [
{ name: 'Ruby 书', price: 59.9, quantity: 2 },
{ name: '键盘', price: 299.0, quantity: 1 },
]
total = calculate_total(items)
# 调试器命令
(rdbg) n # next - 执行下一行
(rdbg) s # step - 步入方法
(rdbg) c # continue - 继续执行
(rdbg) p subtotal # print - 打印变量
(rdbg) pp items # pretty print
(rdbg) info # 显示局部变量
(rdbg) bt # backtrace
(rdbg) watch @total # 监视变量变化
(rdbg) break user.rb:25 # 设置断点
(rdbg) q # 退出
pp 与 p 输出调试
user = { name: '张三', roles: [:admin, :editor], settings: { theme: 'dark', lang: 'zh' } }
p user
pp user
puts user.inspect
users = User.where('age > 25').limit(5)
pp users.map { |u| [u.id, u.name, u.email] }
Pry 交互式控制台
gem install pry pry-byebug
require 'pry'
def process_order(order)
total = order.items.sum(&:price)
binding.pry
apply_discount(total, order.coupon)
end
# Pry 命令
pry(main)> ls # 列出当前作用域的方法和变量
pry(main)> cd User # 进入 User 类的上下文
pry(main)> show-method initialize # 查看方法源码
pry(main)> show-doc Array#map # 查看文档
pry(main)> whereami # 显示当前位置
pry(main)> next # 下一行(pry-byebug)
pry(main)> step # 步入
pry(main)> continue # 继续
💡 调试策略
- 快速检查:
p/pp输出变量值,适合简单问题 - 交互式探索:
binding.pry或binding.break,暂停执行后自由探索 - 生产环境:使用日志(
Logger)而非调试器,结合错误追踪服务(Sentry/Honeybadger) - 性能调试:
benchmark标准库,或stackprof/ruby-profgem
7 本章要点
📦 Bundler
Gemfile 声明依赖,Gemfile.lock 锁定版本;~> 悲观约束最常用
🧪 RSpec
BDD 风格测试;describe/context/it 组织,expect().to 断言,let 惰性求值
🔨 Rake
Ruby 构建工具;task 定义任务,namespace 组织,支持依赖和参数
💎 热门 Gem
Nokogiri 解析 HTML、Puma 服务器、Sidekiq 后台任务、Rails 全栈框架
📏 RuboCop
代码规范检查与自动格式化;-a 安全修复,.rubocop.yml 自定义规则
🐛 调试
debug gem 内置断点调试;Pry 交互式控制台;p/pp 快速输出
🎉 恭喜完成 Ruby 速成教程!
你已经掌握了 Ruby 3.3 的核心知识——从环境搭建、基础语法、方法与面向对象、数据库操作、网络通信、文件处理,到 Bundler 生态与常用工具库。
下一步建议:
- 用 Sinatra 或 Rails 构建一个完整的 Web 应用项目
- 为项目编写 RSpec 测试,养成测试驱动的习惯
- 阅读 Ruby Style Guide,了解社区最佳实践
- 探索 RubyGems.org 上的更多开源 Gem