Ruby on Rails 最佳实践
原创2024/1/18大约 3 分钟
Ruby on Rails 最佳实践
Rails 是一个优雅而强大的 Web 框架,遵循"约定优于配置"的理念,让开发更高效。
Rails 核心理念
MVC 架构
# Model (app/models/user.rb)
class User < ApplicationRecord
has_many :posts
validates :email, presence: true, uniqueness: true
def full_name
"#{first_name} #{last_name}"
end
end
# Controller (app/controllers/users_controller.rb)
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
end
# View (app/views/users/index.html.erb)
<% @users.each do |user| %>
<div class="user">
<%= link_to user.full_name, user_path(user) %>
</div>
<% end %>路由设计
# config/routes.rb
Rails.application.routes.draw do
root 'home#index'
resources :users do
resources :posts, only: [:index, :show]
member do
get 'profile'
end
collection do
get 'search'
end
end
namespace :admin do
resources :users
end
endActive Record
模型关联
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments
has_one :profile
belongs_to :organization, optional: true
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
has_many :tags, through: :post_tags
validates :title, presence: true, length: { minimum: 5 }
validates :content, presence: true
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
end查询优化
# N+1 查询问题
# 坏例子
users = User.all
users.each do |user|
puts user.posts.count # 每次都会查询数据库
end
# 好例子 - 使用 includes
users = User.includes(:posts)
users.each do |user|
puts user.posts.count # 使用预加载的数据
end
# 复杂查询
User.joins(:posts)
.where(posts: { published: true })
.select('users.*, COUNT(posts.id) as posts_count')
.group('users.id')
.having('COUNT(posts.id) > ?', 5)Service Objects
# app/services/user_registration_service.rb
class UserRegistrationService
def initialize(params)
@params = params
end
def call
user = User.new(@params)
ActiveRecord::Base.transaction do
if user.save
send_welcome_email(user)
create_default_profile(user)
{ success: true, user: user }
else
{ success: false, errors: user.errors }
end
end
rescue => e
{ success: false, errors: [e.message] }
end
private
def send_welcome_email(user)
UserMailer.welcome_email(user).deliver_later
end
def create_default_profile(user)
user.create_profile(bio: 'New user')
end
end
# 使用
result = UserRegistrationService.new(user_params).call
if result[:success]
redirect_to user_path(result[:user])
else
render :new, errors: result[:errors]
end后台任务
# app/jobs/report_generation_job.rb
class ReportGenerationJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: 5.seconds, attempts: 3
def perform(user_id, report_type)
user = User.find(user_id)
report = generate_report(user, report_type)
ReportMailer.send_report(user, report).deliver_now
end
private
def generate_report(user, report_type)
# 生成报告逻辑
end
end
# 调用
ReportGenerationJob.perform_later(current_user.id, 'monthly')测试
RSpec 测试
# spec/models/user_spec.rb
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
end
describe 'associations' do
it { should have_many(:posts) }
it { should have_one(:profile) }
end
describe '#full_name' do
it 'returns the full name' do
user = User.new(first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
end
end
# spec/requests/users_spec.rb
RSpec.describe 'Users API', type: :request do
describe 'GET /users' do
it 'returns all users' do
create_list(:user, 3)
get '/users'
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(3)
end
end
endAPI 开发
# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
before_action :authenticate_user!
def index
users = User.page(params[:page]).per(20)
render json: users, each_serializer: UserSerializer
end
def create
user = User.new(user_params)
if user.save
render json: user, status: :created
else
render json: { errors: user.errors }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email, :name, :password)
end
end
end
end
# app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :name, :created_at
has_many :posts
end性能优化
缓存
# 片段缓存
<% cache @product do %>
<%= render @product %>
<% end %>
# 俄罗斯套娃缓存
<% cache ['products', @products.maximum(:updated_at)] do %>
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
<% end %>
# Low-Level 缓存
def expensive_operation
Rails.cache.fetch('expensive_operation', expires_in: 12.hours) do
# 耗时操作
end
end数据库优化
# 添加索引
class AddIndexToUsers < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
add_index :posts, [:user_id, :created_at]
end
end
# 批量操作
User.where(active: false).find_in_batches(batch_size: 1000) do |users|
users.each(&:deactivate!)
end最佳实践
- 遵循 Rails 约定: 减少配置,提高效率
- 保持 Controller 简洁: 业务逻辑放在 Service/Model 中
- 写测试: TDD/BDD 开发流程
- 使用 N+1 检测工具: bullet gem
- 代码审查: Rubocop 静态分析
常用 Gems
# Gemfile
gem 'devise' # 用户认证
gem 'pundit' # 授权
gem 'kaminari' # 分页
gem 'sidekiq' # 后台任务
gem 'fast_jsonapi' # JSON 序列化
gem 'bullet' # N+1 查询检测
gem 'rubocop-rails' # 代码规范总结
Rails 通过约定和最佳实践,让 Web 开发变得优雅而高效。掌握这些最佳实践,能够构建出高质量的 Rails 应用。