ruby-expert
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRuby Expert
Ruby专家
Expert guidance for Ruby development, including Ruby 3+ features, Rails framework, testing with RSpec, and Ruby best practices.
为Ruby开发提供专业指导,包括Ruby 3+特性、Rails框架、RSpec测试以及Ruby最佳实践。
Core Concepts
核心概念
Ruby 3+ Features
Ruby 3+ 新特性
- Pattern matching
- Ractors (parallel execution)
- Fibers (cooperative concurrency)
- Type signatures (RBS)
- Endless methods
- Numbered block parameters
- Hash literal value omission
- 模式匹配
- Ractors(并行执行)
- Fibers(协作式并发)
- 类型签名(RBS)
- 极简方法定义
- 编号块参数
- 哈希字面量值省略
Object-Oriented
面向对象特性
- Everything is an object
- Classes and modules
- Inheritance and mixins
- Method visibility (public, private, protected)
- Singleton methods and eigenclasses
- Duck typing
- 一切皆对象
- 类与模块
- 继承与混入
- 方法可见性(public、private、protected)
- 单例方法与 eigenclass
- 鸭子类型
Functional Features
函数式特性
- Blocks, procs, and lambdas
- Higher-order functions (map, reduce, select)
- Enumerables
- Lazy evaluation
- 块、Proc与Lambda
- 高阶函数(map、reduce、select)
- 可枚举类型
- 惰性求值
Modern Ruby Syntax
现代Ruby语法
Pattern Matching (Ruby 3.0+)
模式匹配(Ruby 3.0+)
ruby
undefinedruby
undefinedCase/in pattern matching
Case/in pattern matching
def process_response(response)
case response
in { status: 200, body: }
puts "Success: #{body}"
in { status: 404 }
puts "Not found"
in { status: 500..599, error: message }
puts "Server error: #{message}"
else
puts "Unknown response"
end
end
def process_response(response)
case response
in { status: 200, body: }
puts "Success: #{body}"
in { status: 404 }
puts "Not found"
in { status: 500..599, error: message }
puts "Server error: #{message}"
else
puts "Unknown response"
end
end
Rightward assignment
Rightward assignment
response = { status: 200, body: "OK" }
response => { status:, body: }
puts status # 200
puts body # "OK"
response = { status: 200, body: "OK" }
response => { status:, body: }
puts status # 200
puts body # "OK"
Array pattern matching
Array pattern matching
def summarize(data)
case data
in []
"Empty"
in [item]
"Single item: #{item}"
in [first, *rest]
"First: #{first}, Rest: #{rest.length} items"
end
end
undefineddef summarize(data)
case data
in []
"Empty"
in [item]
"Single item: #{item}"
in [first, *rest]
"First: #{first}, Rest: #{rest.length} items"
end
end
undefinedEndless Methods
极简方法定义
ruby
undefinedruby
undefinedSingle-line method definition
Single-line method definition
def greet(name) = "Hello, #{name}!"
def square(x) = x * x
def full_name = "#{first_name} #{last_name}"
class User
attr_reader :name, :email
def initialize(name:, email:) = (@name = name; @email = email)
def admin? = @role == :admin
end
undefineddef greet(name) = "Hello, #{name}!"
def square(x) = x * x
def full_name = "#{first_name} #{last_name}"
class User
attr_reader :name, :email
def initialize(name:, email:) = (@name = name; @email = email)
def admin? = @role == :admin
end
undefinedNumbered Block Parameters
编号块参数
ruby
undefinedruby
undefinedUse _1, _2, etc. for block parameters
Use _1, _2, etc. for block parameters
[1, 2, 3].map { _1 * 2 } # [2, 4, 6]
{ a: 1, b: 2 }.map { "#{_1}: #{_2}" } # ["a: 1", "b: 2"]
users.sort_by { [_1.last_name, _1.first_name] }
undefined[1, 2, 3].map { _1 * 2 } # [2, 4, 6]
{ a: 1, b: 2 }.map { "#{_1}: #{_2}" } # ["a: 1", "b: 2"]
users.sort_by { [_1.last_name, _1.first_name] }
undefinedHash Literal Value Omission
哈希字面量值省略
ruby
name = "Alice"
age = 30
email = "alice@example.com"ruby
name = "Alice"
age = 30
email = "alice@example.com"Before
Before
user = { name: name, age: age, email: email }
user = { name: name, age: age, email: email }
After (Ruby 3.1+)
After (Ruby 3.1+)
user = { name:, age:, email: }
undefineduser = { name:, age:, email: }
undefinedRuby on Rails
Ruby on Rails
Rails 7+ Application
Rails 7+ 应用示例
ruby
undefinedruby
undefinedapp/models/user.rb
app/models/user.rb
class User < ApplicationRecord
Validations
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
validates :age, numericality: { greater_than_or_equal_to: 18 }, allow_nil: true
Associations
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_posts, through: :likes, source: :post
Scopes
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
scope :with_posts, -> { joins(:posts).distinct }
Callbacks
before_save :normalize_email
after_create :send_welcome_email
Enums
enum role: { user: 0, moderator: 1, admin: 2 }
Instance methods
def full_name
"#{first_name} #{last_name}"
end
def admin?
role == 'admin'
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
class User < ApplicationRecord
Validations
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
validates :age, numericality: { greater_than_or_equal_to: 18 }, allow_nil: true
Associations
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_posts, through: :likes, source: :post
Scopes
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
scope :with_posts, -> { joins(:posts).distinct }
Callbacks
before_save :normalize_email
after_create :send_welcome_email
Enums
enum role: { user: 0, moderator: 1, admin: 2 }
Instance methods
def full_name
"#{first_name} #{last_name}"
end
def admin?
role == 'admin'
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
app/models/post.rb
app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liking_users, through: :likes, source: :user
has_one_attached :cover_image
has_rich_text :content
validates :title, presence: true, length: { minimum: 5, maximum: 200 }
validates :content, presence: true
scope :published, -> { where(published: true) }
scope :by_user, ->(user) { where(user: user) }
scope :search, ->(query) { where('title ILIKE ? OR content ILIKE ?', "%#{query}%", "%#{query}%") }
before_save :generate_slug
def publish!
update!(published: true, published_at: Time.current)
end
private
def generate_slug
self.slug = title.parameterize
end
end
undefinedclass Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liking_users, through: :likes, source: :user
has_one_attached :cover_image
has_rich_text :content
validates :title, presence: true, length: { minimum: 5, maximum: 200 }
validates :content, presence: true
scope :published, -> { where(published: true) }
scope :by_user, ->(user) { where(user: user) }
scope :search, ->(query) { where('title ILIKE ? OR content ILIKE ?', "%#{query}%", "%#{query}%") }
before_save :generate_slug
def publish!
update!(published: true, published_at: Time.current)
end
private
def generate_slug
self.slug = title.parameterize
end
end
undefinedControllers
控制器示例
ruby
undefinedruby
undefinedapp/controllers/api/v1/posts_controller.rb
app/controllers/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :update, :destroy]
before_action :authorize_post, only: [:update, :destroy]
# GET /api/v1/posts
def index
@posts = Post.published
.includes(:user, :comments)
.page(params[:page])
.per(20)
render json: @posts, each_serializer: PostSerializer
end
# GET /api/v1/posts/:id
def show
render json: @post, serializer: PostSerializer, include: [:user, :comments]
end
# POST /api/v1/posts
def create
@post = current_user.posts.build(post_params)
if @post.save
render json: @post, serializer: PostSerializer, status: :created
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
# PATCH/PUT /api/v1/posts/:id
def update
if @post.update(post_params)
render json: @post, serializer: PostSerializer
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
# DELETE /api/v1/posts/:id
def destroy
@post.destroy
head :no_content
end
private
def set_post
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Post not found' }, status: :not_found
end
def authorize_post
unless @post.user == current_user || current_user.admin?
render json: { error: 'Unauthorized' }, status: :forbidden
end
end
def post_params
params.require(:post).permit(:title, :content, :published, :cover_image)
end
endend
end
undefinedmodule Api
module V1
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :update, :destroy]
before_action :authorize_post, only: [:update, :destroy]
# GET /api/v1/posts
def index
@posts = Post.published
.includes(:user, :comments)
.page(params[:page])
.per(20)
render json: @posts, each_serializer: PostSerializer
end
# GET /api/v1/posts/:id
def show
render json: @post, serializer: PostSerializer, include: [:user, :comments]
end
# POST /api/v1/posts
def create
@post = current_user.posts.build(post_params)
if @post.save
render json: @post, serializer: PostSerializer, status: :created
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
# PATCH/PUT /api/v1/posts/:id
def update
if @post.update(post_params)
render json: @post, serializer: PostSerializer
else
render json: { errors: @post.errors.full_messages }, status: :unprocessable_entity
end
end
# DELETE /api/v1/posts/:id
def destroy
@post.destroy
head :no_content
end
private
def set_post
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Post not found' }, status: :not_found
end
def authorize_post
unless @post.user == current_user || current_user.admin?
render json: { error: 'Unauthorized' }, status: :forbidden
end
end
def post_params
params.require(:post).permit(:title, :content, :published, :cover_image)
end
endend
end
undefinedActive Record Queries
Active Record 查询
ruby
undefinedruby
undefinedEfficient queries
Efficient queries
User.includes(:posts).where(posts: { published: true })
User.joins(:posts).group('users.id').having('COUNT(posts.id) > ?', 5)
User.left_joins(:posts).where(posts: { id: nil }) # Users with no posts
User.includes(:posts).where(posts: { published: true })
User.joins(:posts).group('users.id').having('COUNT(posts.id) > ?', 5)
User.left_joins(:posts).where(posts: { id: nil }) # Users with no posts
Complex queries
Complex queries
Post.where('created_at > ?', 1.week.ago)
.where(published: true)
.order(created_at: :desc)
.limit(10)
Post.where('created_at > ?', 1.week.ago)
.where(published: true)
.order(created_at: :desc)
.limit(10)
Find or create
Find or create
user = User.find_or_create_by(email: 'user@example.com') do |u|
u.name = 'New User'
u.role = :user
end
user = User.find_or_create_by(email: 'user@example.com') do |u|
u.name = 'New User'
u.role = :user
end
Upsert (Rails 6+)
Upsert (Rails 6+)
User.upsert({ email: 'user@example.com', name: 'Alice' }, unique_by: :email)
User.upsert({ email: 'user@example.com', name: 'Alice' }, unique_by: :email)
Batch processing
Batch processing
User.find_each(batch_size: 100) do |user|
user.update_subscription_status
end
User.find_each(batch_size: 100) do |user|
user.update_subscription_status
end
Transactions
Transactions
ActiveRecord::Base.transaction do
user.update!(balance: user.balance - amount)
recipient.update!(balance: recipient.balance + amount)
Transaction.create!(from: user, to: recipient, amount: amount)
end
ActiveRecord::Base.transaction do
user.update!(balance: user.balance - amount)
recipient.update!(balance: recipient.balance + amount)
Transaction.create!(from: user, to: recipient, amount: amount)
end
Raw SQL (when needed)
Raw SQL (when needed)
ActiveRecord::Base.connection.execute(
"SELECT * FROM users WHERE created_at > '2024-01-01'"
)
undefinedActiveRecord::Base.connection.execute(
"SELECT * FROM users WHERE created_at > '2024-01-01'"
)
undefinedBackground Jobs (Sidekiq)
后台任务(Sidekiq)
ruby
undefinedruby
undefinedapp/jobs/send_email_job.rb
app/jobs/send_email_job.rb
class SendEmailJob < ApplicationJob
queue_as :default
retry_on Net::SMTPServerBusy, wait: :exponentially_longer
def perform(user_id, email_type)
user = User.find(user_id)
case email_type
when 'welcome'
UserMailer.welcome(user).deliver_now
when 'notification'
UserMailer.notification(user).deliver_now
end
end
end
class SendEmailJob < ApplicationJob
queue_as :default
retry_on Net::SMTPServerBusy, wait: :exponentially_longer
def perform(user_id, email_type)
user = User.find(user_id)
case email_type
when 'welcome'
UserMailer.welcome(user).deliver_now
when 'notification'
UserMailer.notification(user).deliver_now
end
end
end
Usage
Usage
SendEmailJob.perform_later(user.id, 'welcome')
SendEmailJob.set(wait: 1.hour).perform_later(user.id, 'notification')
undefinedSendEmailJob.perform_later(user.id, 'welcome')
SendEmailJob.set(wait: 1.hour).perform_later(user.id, 'notification')
undefinedMailers
邮件发送器
ruby
undefinedruby
undefinedapp/mailers/user_mailer.rb
app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: 'noreply@example.com'
def welcome(user)
@user = user
@url = 'https://example.com/login'
mail(to: @user.email, subject: 'Welcome to My App')
end
def notification(user, message)
@user = user
@message = message
mail(
to: @user.email,
subject: 'New Notification',
reply_to: 'support@example.com'
)
end
end
undefinedclass UserMailer < ApplicationMailer
default from: 'noreply@example.com'
def welcome(user)
@user = user
@url = 'https://example.com/login'
mail(to: @user.email, subject: 'Welcome to My App')
end
def notification(user, message)
@user = user
@message = message
mail(
to: @user.email,
subject: 'New Notification',
reply_to: 'support@example.com'
)
end
end
undefinedRoutes
路由配置
ruby
undefinedruby
undefinedconfig/routes.rb
config/routes.rb
Rails.application.routes.draw do
root 'home#index'
RESTful resources
resources :posts do
member do
post :publish
post :like
end
collection do
get :trending
end
resources :comments, only: [:create, :destroy]
end
Nested resources
resources :users do
resources :posts, only: [:index, :show]
end
Namespaced routes
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
resources :users, only: [:index, :show]
end
end
Constraints
constraints(subdomain: 'api') do
scope module: 'api' do
resources :posts
end
end
Custom routes
get '/about', to: 'pages#about'
post '/contact', to: 'pages#contact'
end
undefinedRails.application.routes.draw do
root 'home#index'
RESTful resources
resources :posts do
member do
post :publish
post :like
end
collection do
get :trending
end
resources :comments, only: [:create, :destroy]
end
Nested resources
resources :users do
resources :posts, only: [:index, :show]
end
Namespaced routes
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
resources :users, only: [:index, :show]
end
end
Constraints
constraints(subdomain: 'api') do
scope module: 'api' do
resources :posts
end
end
Custom routes
get '/about', to: 'pages#about'
post '/contact', to: 'pages#contact'
end
undefinedTesting with RSpec
RSpec测试
Model Specs
模型测试
ruby
undefinedruby
undefinedspec/models/user_spec.rb
spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_presence_of(:name) }
it { should validate_length_of(:name).is_at_least(2).is_at_most(100) }
end
describe 'associations' do
it { should have_many(:posts).dependent(:destroy) }
it { should have_many(:comments).dependent(:destroy) }
end
describe '#full_name' do
it 'returns the full name' do
user = User.new(first_name: 'Alice', last_name: 'Smith')
expect(user.full_name).to eq('Alice Smith')
end
end
describe '#admin?' do
it 'returns true for admin users' do
user = User.new(role: :admin)
expect(user).to be_admin
end
it 'returns false for regular users' do
user = User.new(role: :user)
expect(user).not_to be_admin
endend
describe 'callbacks' do
it 'normalizes email before save' do
user = create(:user, email: ' Alice@Example.COM ')
expect(user.email).to eq('alice@example.com')
end
it 'sends welcome email after create' do
expect {
create(:user)
}.to have_enqueued_job(SendEmailJob).with(anything, 'welcome')
endend
end
undefinedrequire 'rails_helper'
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_presence_of(:name) }
it { should validate_length_of(:name).is_at_least(2).is_at_most(100) }
end
describe 'associations' do
it { should have_many(:posts).dependent(:destroy) }
it { should have_many(:comments).dependent(:destroy) }
end
describe '#full_name' do
it 'returns the full name' do
user = User.new(first_name: 'Alice', last_name: 'Smith')
expect(user.full_name).to eq('Alice Smith')
end
end
describe '#admin?' do
it 'returns true for admin users' do
user = User.new(role: :admin)
expect(user).to be_admin
end
it 'returns false for regular users' do
user = User.new(role: :user)
expect(user).not_to be_admin
endend
describe 'callbacks' do
it 'normalizes email before save' do
user = create(:user, email: ' Alice@Example.COM ')
expect(user.email).to eq('alice@example.com')
end
it 'sends welcome email after create' do
expect {
create(:user)
}.to have_enqueued_job(SendEmailJob).with(anything, 'welcome')
endend
end
undefinedController Specs
控制器测试
ruby
undefinedruby
undefinedspec/controllers/posts_controller_spec.rb
spec/controllers/posts_controller_spec.rb
require 'rails_helper'
RSpec.describe PostsController, type: :controller do
let(:user) { create(:user) }
let(:post) { create(:post, user: user) }
describe 'GET #index' do
it 'returns a success response' do
get :index
expect(response).to have_http_status(:success)
end
it 'assigns @posts' do
post1 = create(:post, published: true)
post2 = create(:post, published: true)
get :index
expect(assigns(:posts)).to match_array([post1, post2])
endend
describe 'POST #create' do
context 'when authenticated' do
before { sign_in user }
context 'with valid params' do
let(:valid_params) { { post: { title: 'Test Post', content: 'Content' } } }
it 'creates a new post' do
expect {
post :create, params: valid_params
}.to change(Post, :count).by(1)
end
it 'returns created status' do
post :create, params: valid_params
expect(response).to have_http_status(:created)
end
end
context 'with invalid params' do
let(:invalid_params) { { post: { title: '' } } }
it 'does not create a post' do
expect {
post :create, params: invalid_params
}.not_to change(Post, :count)
end
it 'returns unprocessable entity status' do
post :create, params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
context 'when not authenticated' do
it 'returns unauthorized status' do
post :create, params: { post: { title: 'Test' } }
expect(response).to have_http_status(:unauthorized)
end
endend
end
undefinedrequire 'rails_helper'
RSpec.describe PostsController, type: :controller do
let(:user) { create(:user) }
let(:post) { create(:post, user: user) }
describe 'GET #index' do
it 'returns a success response' do
get :index
expect(response).to have_http_status(:success)
end
it 'assigns @posts' do
post1 = create(:post, published: true)
post2 = create(:post, published: true)
get :index
expect(assigns(:posts)).to match_array([post1, post2])
endend
describe 'POST #create' do
context 'when authenticated' do
before { sign_in user }
context 'with valid params' do
let(:valid_params) { { post: { title: 'Test Post', content: 'Content' } } }
it 'creates a new post' do
expect {
post :create, params: valid_params
}.to change(Post, :count).by(1)
end
it 'returns created status' do
post :create, params: valid_params
expect(response).to have_http_status(:created)
end
end
context 'with invalid params' do
let(:invalid_params) { { post: { title: '' } } }
it 'does not create a post' do
expect {
post :create, params: invalid_params
}.not_to change(Post, :count)
end
it 'returns unprocessable entity status' do
post :create, params: invalid_params
expect(response).to have_http_status(:unprocessable_entity)
end
end
end
context 'when not authenticated' do
it 'returns unauthorized status' do
post :create, params: { post: { title: 'Test' } }
expect(response).to have_http_status(:unauthorized)
end
endend
end
undefinedRequest Specs
请求测试
ruby
undefinedruby
undefinedspec/requests/api/v1/posts_spec.rb
spec/requests/api/v1/posts_spec.rb
require 'rails_helper'
RSpec.describe 'Api::V1::Posts', type: :request do
let(:user) { create(:user) }
let(:headers) { { 'Authorization' => "Bearer #{user.auth_token}" } }
describe 'GET /api/v1/posts' do
before do
create_list(:post, 3, published: true)
create(:post, published: false)
end
it 'returns published posts' do
get '/api/v1/posts'
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body).length).to eq(3)
end
it 'paginates results' do
create_list(:post, 25, published: true)
get '/api/v1/posts', params: { page: 2 }
expect(JSON.parse(response.body).length).to eq(8) # 28 total, 20 per page
endend
describe 'POST /api/v1/posts' do
context 'with valid params' do
let(:valid_params) do
{ post: { title: 'Test Post', content: 'Content' } }
end
it 'creates a post' do
expect {
post '/api/v1/posts', params: valid_params, headers: headers
}.to change(Post, :count).by(1)
end
it 'returns the created post' do
post '/api/v1/posts', params: valid_params, headers: headers
expect(response).to have_http_status(:created)
expect(JSON.parse(response.body)['title']).to eq('Test Post')
end
endend
end
undefinedrequire 'rails_helper'
RSpec.describe 'Api::V1::Posts', type: :request do
let(:user) { create(:user) }
let(:headers) { { 'Authorization' => "Bearer #{user.auth_token}" } }
describe 'GET /api/v1/posts' do
before do
create_list(:post, 3, published: true)
create(:post, published: false)
end
it 'returns published posts' do
get '/api/v1/posts'
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body).length).to eq(3)
end
it 'paginates results' do
create_list(:post, 25, published: true)
get '/api/v1/posts', params: { page: 2 }
expect(JSON.parse(response.body).length).to eq(8) # 28 total, 20 per page
endend
describe 'POST /api/v1/posts' do
context 'with valid params' do
let(:valid_params) do
{ post: { title: 'Test Post', content: 'Content' } }
end
it 'creates a post' do
expect {
post '/api/v1/posts', params: valid_params, headers: headers
}.to change(Post, :count).by(1)
end
it 'returns the created post' do
post '/api/v1/posts', params: valid_params, headers: headers
expect(response).to have_http_status(:created)
expect(JSON.parse(response.body)['title']).to eq('Test Post')
end
endend
end
undefinedFactoryBot
FactoryBot工厂
ruby
undefinedruby
undefinedspec/factories/users.rb
spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
name { Faker::Name.name }
password { 'password123' }
role { :user }
trait :admin do
role { :admin }
end
trait :with_posts do
transient do
posts_count { 5 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
endend
factory :post do
association :user
title { Faker::Lorem.sentence }
content { Faker::Lorem.paragraph }
published { false }
trait :published do
published { true }
published_at { Time.current }
endend
end
FactoryBot.define do
factory :user do
sequence(:email) { |n| "user#{n}@example.com" }
name { Faker::Name.name }
password { 'password123' }
role { :user }
trait :admin do
role { :admin }
end
trait :with_posts do
transient do
posts_count { 5 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
endend
factory :post do
association :user
title { Faker::Lorem.sentence }
content { Faker::Lorem.paragraph }
published { false }
trait :published do
published { true }
published_at { Time.current }
endend
end
Usage
Usage
user = create(:user)
admin = create(:user, :admin)
user_with_posts = create(:user, :with_posts, posts_count: 10)
published_post = create(:post, :published)
undefineduser = create(:user)
admin = create(:user, :admin)
user_with_posts = create(:user, :with_posts, posts_count: 10)
published_post = create(:post, :published)
undefinedMetaprogramming
元编程
ruby
undefinedruby
undefinedDefine methods dynamically
Define methods dynamically
class User
%w[name email phone].each do |attr|
define_method("#{attr}_present?") do
send(attr).present?
end
end
end
class User
%w[name email phone].each do |attr|
define_method("#{attr}_present?") do
send(attr).present?
end
end
end
Method missing
Method missing
class Configuration
def initialize
@settings = {}
end
def method_missing(method, *args)
method_name = method.to_s
if method_name.end_with?('=')
@settings[method_name.chomp('=')] = args.first
else
@settings[method_name]
end
end
def respond_to_missing?(method, include_private = false)
true
end
end
config = Configuration.new
config.api_key = 'secret'
config.api_key # => 'secret'
class Configuration
def initialize
@settings = {}
end
def method_missing(method, *args)
method_name = method.to_s
if method_name.end_with?('=')
@settings[method_name.chomp('=')] = args.first
else
@settings[method_name]
end
end
def respond_to_missing?(method, include_private = false)
true
end
end
config = Configuration.new
config.api_key = 'secret'
config.api_key # => 'secret'
Class macros
Class macros
module Timestampable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def timestampable
before_save :set_timestamps
define_method(:set_timestamps) do
self.updated_at = Time.current
self.created_at ||= Time.current
end
endend
end
class Post
include Timestampable
timestampable
end
undefinedmodule Timestampable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def timestampable
before_save :set_timestamps
define_method(:set_timestamps) do
self.updated_at = Time.current
self.created_at ||= Time.current
end
endend
end
class Post
include Timestampable
timestampable
end
undefinedBest Practices
最佳实践
Code Style
代码风格
- Follow Ruby Style Guide
- Use 2-space indentation
- Prefer symbols over strings for keys
- Use for methods and variables
snake_case - Use for classes
CamelCase - Use meaningful variable names
- 遵循Ruby风格指南
- 使用2空格缩进
- 优先使用符号而非字符串作为键
- 方法和变量使用命名
snake_case - 类使用命名
CamelCase - 使用有意义的变量名
Performance
性能优化
- Use to avoid N+1 queries
includes - Add database indexes on foreign keys
- Use to load only needed columns
select - Use for large datasets
find_each - Cache expensive computations
- 使用避免N+1查询
includes - 为外键添加数据库索引
- 使用仅加载所需列
select - 处理大数据集时使用
find_each - 缓存耗时计算结果
Security
安全规范
- Use strong parameters in controllers
- Sanitize user input
- Protect against SQL injection (use parameterized queries)
- Use CSRF protection
- Implement authentication and authorization
- Keep dependencies updated
- 控制器中使用强参数
- 对用户输入进行 sanitize 处理
- 防范SQL注入(使用参数化查询)
- 启用CSRF保护
- 实现身份认证与授权
- 及时更新依赖包
Anti-Patterns to Avoid
需避免的反模式
❌ Fat models: Extract logic to service objects
❌ N+1 queries: Use or
❌ Long controller actions: Extract to services
❌ Missing indexes: Add indexes on foreign keys
❌ Skipping validations: Always validate data
❌ Exposing internals: Use serializers for API responses
❌ Global state: Avoid global variables and class variables
includeseager_load❌ 胖模型:将逻辑提取到服务对象中
❌ N+1查询:使用或
❌ 控制器动作过长:提取到服务层
❌ 缺失索引:为外键添加索引
❌ 跳过验证:始终对数据进行验证
❌ 暴露内部实现:API响应使用序列化器
❌ 全局状态:避免全局变量和类变量
includeseager_loadResources
参考资源
- Ruby Documentation: https://ruby-doc.org/
- Rails Guides: https://guides.rubyonrails.org/
- RSpec: https://rspec.info/
- Ruby Style Guide: https://rubystyle.guide/
- Rails API: https://api.rubyonrails.org/
- Ruby文档:https://ruby-doc.org/
- Rails指南:https://guides.rubyonrails.org/
- RSpec官网:https://rspec.info/
- Ruby风格指南:https://rubystyle.guide/
- Rails API文档:https://api.rubyonrails.org/