ruby-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Ruby 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
undefined
ruby
undefined

Case/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
undefined
def summarize(data) case data in [] "Empty" in [item] "Single item: #{item}" in [first, *rest] "First: #{first}, Rest: #{rest.length} items" end end
undefined

Endless Methods

极简方法定义

ruby
undefined
ruby
undefined

Single-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
undefined
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
undefined

Numbered Block Parameters

编号块参数

ruby
undefined
ruby
undefined

Use _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] }
undefined

Hash 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: }
undefined
user = { name:, age:, email: }
undefined

Ruby on Rails

Ruby on Rails

Rails 7+ Application

Rails 7+ 应用示例

ruby
undefined
ruby
undefined

app/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
undefined
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
undefined

Controllers

控制器示例

ruby
undefined
ruby
undefined

app/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
end
end end
undefined
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
end
end end
undefined

Active Record Queries

Active Record 查询

ruby
undefined
ruby
undefined

Efficient 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'" )
undefined
ActiveRecord::Base.connection.execute( "SELECT * FROM users WHERE created_at > '2024-01-01'" )
undefined

Background Jobs (Sidekiq)

后台任务(Sidekiq)

ruby
undefined
ruby
undefined

app/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')
undefined
SendEmailJob.perform_later(user.id, 'welcome') SendEmailJob.set(wait: 1.hour).perform_later(user.id, 'notification')
undefined

Mailers

邮件发送器

ruby
undefined
ruby
undefined

app/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
undefined
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
undefined

Routes

路由配置

ruby
undefined
ruby
undefined

config/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
undefined
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
undefined

Testing with RSpec

RSpec测试

Model Specs

模型测试

ruby
undefined
ruby
undefined

spec/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
end
end
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')
end
end end
undefined
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
end
end
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')
end
end end
undefined

Controller Specs

控制器测试

ruby
undefined
ruby
undefined

spec/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])
end
end
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
end
end end
undefined
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])
end
end
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
end
end end
undefined

Request Specs

请求测试

ruby
undefined
ruby
undefined

spec/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
end
end
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
end
end end
undefined
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
end
end
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
end
end end
undefined

FactoryBot

FactoryBot工厂

ruby
undefined
ruby
undefined

spec/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
end
end
factory :post do association :user title { Faker::Lorem.sentence } content { Faker::Lorem.paragraph } published { false }
trait :published do
  published { true }
  published_at { Time.current }
end
end 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
end
end
factory :post do association :user title { Faker::Lorem.sentence } content { Faker::Lorem.paragraph } published { false }
trait :published do
  published { true }
  published_at { Time.current }
end
end 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)
undefined
user = create(:user) admin = create(:user, :admin) user_with_posts = create(:user, :with_posts, posts_count: 10) published_post = create(:post, :published)
undefined

Metaprogramming

元编程

ruby
undefined
ruby
undefined

Define 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
end
end end
class Post include Timestampable timestampable end
undefined
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
end
end end
class Post include Timestampable timestampable end
undefined

Best Practices

最佳实践

Code Style

代码风格

  • Follow Ruby Style Guide
  • Use 2-space indentation
  • Prefer symbols over strings for keys
  • Use
    snake_case
    for methods and variables
  • Use
    CamelCase
    for classes
  • Use meaningful variable names
  • 遵循Ruby风格指南
  • 使用2空格缩进
  • 优先使用符号而非字符串作为键
  • 方法和变量使用
    snake_case
    命名
  • 类使用
    CamelCase
    命名
  • 使用有意义的变量名

Performance

性能优化

  • Use
    includes
    to avoid N+1 queries
  • Add database indexes on foreign keys
  • Use
    select
    to load only needed columns
  • Use
    find_each
    for large datasets
  • Cache expensive computations
  • 使用
    includes
    避免N+1查询
  • 为外键添加数据库索引
  • 使用
    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
includes
or
eager_load
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
胖模型:将逻辑提取到服务对象中 ❌ N+1查询:使用
includes
eager_load
控制器动作过长:提取到服务层 ❌ 缺失索引:为外键添加索引 ❌ 跳过验证:始终对数据进行验证 ❌ 暴露内部实现:API响应使用序列化器 ❌ 全局状态:避免全局变量和类变量

Resources

参考资源