rails
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRuby on Rails Guide
Ruby on Rails 指南
Applies to: Rails 7+, Ruby 3.2+, Hotwire/Turbo, ActiveRecord, Action Cable
适用范围:Rails 7+, Ruby 3.2+, Hotwire/Turbo, ActiveRecord, Action Cable
Core Principles
核心原则
- Convention over Configuration: Follow Rails conventions -- naming, directory structure, RESTful routes
- Fat Models, Thin Controllers: Business logic in models and service objects, controllers handle request/response only
- DRY: Use concerns, partials, helpers, and shared services to avoid repetition
- RESTful by Default: Design resources around standard CRUD actions before adding custom routes
- Security by Default: CSRF, XSS protection, strong parameters, and parameterized queries are built in
- 约定优于配置:遵循Rails约定——命名规范、目录结构、RESTful路由
- 胖模型,瘦控制器:业务逻辑放在模型和服务对象中,控制器仅负责处理请求/响应
- DRY:使用concerns、partials、helpers和共享服务避免重复代码
- 默认遵循RESTful:在添加自定义路由前,围绕标准CRUD操作设计资源
- 默认安全:内置CSRF、XSS防护、强参数和参数化查询能力
When to Use Rails
何时使用Rails
Good fit: Full-stack web apps, MVPs, CMS, e-commerce, SaaS, Hotwire/Turbo SPA-like UX, API backends.
Consider alternatives: Minimal microservices (Sinatra, Hanami), heavy real-time streaming, strict type safety needs (Rust, Go).
适用场景:全栈Web应用、MVP、CMS、电商平台、SaaS、Hotwire/Turbo实现类SPA用户体验、API后端。
可考虑替代方案:极简微服务(Sinatra、Hanami)、重度实时流场景、严格类型安全需求(Rust、Go)。
Project Structure
项目结构
myapp/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ ├── concerns/ # Controller concerns (auth, pagination)
│ │ └── api/
│ │ └── v1/ # Versioned API controllers
│ ├── models/
│ │ ├── application_record.rb
│ │ └── concerns/ # Model concerns (searchable, sluggable)
│ ├── views/
│ │ ├── layouts/ # Application layouts
│ │ │ └── application.html.erb
│ │ ├── shared/ # Shared partials (_navbar, _footer)
│ │ └── posts/ # Resource-specific views
│ ├── helpers/ # View helpers
│ ├── jobs/ # ActiveJob classes
│ ├── mailers/ # Action Mailer classes
│ ├── channels/ # Action Cable channels
│ └── services/ # Service objects (custom directory)
├── config/
│ ├── routes.rb # Route definitions
│ ├── database.yml # Database configuration
│ ├── environments/ # Per-environment settings
│ └── initializers/ # Boot-time configuration
├── db/
│ ├── migrate/ # Migration files
│ ├── schema.rb # Current schema snapshot
│ └── seeds.rb # Seed data
├── lib/
│ └── tasks/ # Custom Rake tasks
├── test/ # Minitest (default) or spec/ for RSpec
│ ├── models/
│ ├── controllers/
│ ├── integration/
│ └── system/
├── Gemfile
└── Gemfile.lock- Place business logic in ; use concerns for shared model/controller behavior
app/services/ - Namespace API controllers under ; keep shared partials in
Api::V1views/shared/
myapp/
├── app/
│ ├── controllers/
│ │ ├── application_controller.rb
│ │ ├── concerns/ # Controller concerns (auth, pagination)
│ │ └── api/
│ │ └── v1/ # Versioned API controllers
│ ├── models/
│ │ ├── application_record.rb
│ │ └── concerns/ # Model concerns (searchable, sluggable)
│ ├── views/
│ │ ├── layouts/ # Application layouts
│ │ │ └── application.html.erb
│ │ ├── shared/ # Shared partials (_navbar, _footer)
│ │ └── posts/ # Resource-specific views
│ ├── helpers/ # View helpers
│ ├── jobs/ # ActiveJob classes
│ ├── mailers/ # Action Mailer classes
│ ├── channels/ # Action Cable channels
│ └── services/ # Service objects (custom directory)
├── config/
│ ├── routes.rb # Route definitions
│ ├── database.yml # Database configuration
│ ├── environments/ # Per-environment settings
│ └── initializers/ # Boot-time configuration
├── db/
│ ├── migrate/ # Migration files
│ ├── schema.rb # Current schema snapshot
│ └── seeds.rb # Seed data
├── lib/
│ └── tasks/ # Custom Rake tasks
├── test/ # Minitest (default) or spec/ for RSpec
│ ├── models/
│ ├── controllers/
│ ├── integration/
│ └── system/
├── Gemfile
└── Gemfile.lock- 业务逻辑放在目录下;共享的模型/控制器逻辑使用concerns实现
app/services/ - API控制器放在命名空间下;共享partial放在
Api::V1目录下views/shared/
Guardrails
开发规范
Controllers
控制器
- Keep controllers under 100 lines total
- Limit each action to 10 lines (delegate to services for complex logic)
- Always use for authentication and resource loading
before_action - Always use strong parameters via private methods
*_params - Return for failed form submissions
status: :unprocessable_entity - Use (303) for
status: :see_otherafter DELETEredirect_to - Use blocks when serving multiple formats
respond_to
- 控制器总代码行数控制在100行以内
- 每个action代码行数限制在10行以内(复杂逻辑委托给服务对象处理)
- 认证和资源加载统一使用实现
before_action - 始终通过私有方法使用强参数
*_params - 表单提交失败时返回
status: :unprocessable_entity - DELETE操作后的使用
redirect_to(303)status: :see_other - 支持多种返回格式时使用代码块
respond_to
Models
模型
- Always add validations for required fields and constraints
- Always add option on
dependent:/has_manyassociationshas_one - Use for reusable queries (never build queries in controllers)
scope - Use with explicit integer or string mappings
enum - Use for data normalization (downcase, strip)
before_validation - Avoid heavy logic in callbacks -- prefer service objects for side effects
- Always define for belongs_to when parent displays counts
counter_cache: true
- 始终为必填字段和约束添加校验规则
- /
has_many关联始终添加has_one选项dependent: - 可复用查询使用实现(禁止在控制器中拼接查询)
scope - 使用显式的整数或字符串映射
enum - 数据标准化(转小写、去空格)使用实现
before_validation - 回调中避免放置复杂逻辑,副作用处理优先使用服务对象
- 父模型需要显示关联计数时,始终定义
belongs_tocounter_cache: true
Migrations
迁移
- Always add for required columns
null: false - Always add database indexes for foreign keys and frequently queried columns
- Always add unique indexes where uniqueness is required
- Use with
referencesfor associationsforeign_key: true - Set sensible defaults with for boolean and status columns
default: - Include both and
upmethods for irreversible migrationsdown - Never modify a migration after it has been applied to production
- 必填列始终添加约束
null: false - 外键和高频查询列始终添加数据库索引
- 需要唯一性约束的字段始终添加唯一索引
- 关联使用带的
foreign_key: true定义references - 布尔和状态列设置合理的默认值
default: - 不可逆的迁移需要同时定义和
up方法down - 迁移已应用到生产环境后禁止修改
Security
安全
- Strong parameters: never use (permit all)
params.permit! - CSRF protection: enabled by default, skip only for API controllers with token auth
- SQL injection: use ActiveRecord query methods, never string interpolation in queries
- XSS: ERB auto-escapes by default; never use or
rawwith user datahtml_safe - Mass assignment: only permit explicitly needed attributes
- Secrets: use or environment variables
Rails.application.credentials - Set in production
force_ssl - Use configuration in production
content_security_policy
- 强参数:禁止使用(允许所有参数)
params.permit! - CSRF防护:默认开启,仅使用token认证的API控制器可跳过
- SQL注入:使用ActiveRecord查询方法,禁止在查询中使用字符串插值
- XSS防护:ERB默认自动转义,用户输入内容禁止使用或
rawhtml_safe - 批量赋值:仅显式允许需要的属性
- 密钥:使用或环境变量存储
Rails.application.credentials - 生产环境开启
force_ssl - 生产环境配置
content_security_policy
Performance
性能
- Always use (or
includes/preload) to prevent N+1 querieseager_load - Add database indexes for all foreign keys and commonly filtered columns
- Use for association counts displayed in views
counter_cache - Use pagination for all list endpoints (Kaminari or Pagy)
- Use fragment caching () for expensive view rendering
cache @record do - Use background jobs (ActiveJob + Sidekiq) for slow operations
- Use instead of
find_eachwhen iterating over large datasetseach
- 始终使用(或
includes/preload)避免N+1查询eager_load - 所有外键和常用过滤列添加数据库索引
- 视图中展示的关联计数使用
counter_cache - 所有列表接口使用分页(Kaminari或Pagy)
- 耗时的视图渲染使用片段缓存()
cache @record do - 慢操作使用后台任务(ActiveJob + Sidekiq)处理
- 遍历大量数据集时使用替代
find_eacheach
MVC Conventions
MVC约定
Models
模型
ruby
undefinedruby
undefinedapp/models/post.rb
app/models/post.rb
class Post < ApplicationRecord
1. Associations
belongs_to :user
belongs_to :category, optional: true
has_many :comments, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
has_one_attached :featured_image
2. Validations
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true
3. Enums
enum :status, { draft: 0, published: 1, archived: 2 }
4. Scopes
scope :published, -> { where(status: :published) }
scope :recent, -> { order(created_at: :desc) }
scope :by_category, ->(cat) { where(category: cat) }
scope :search, ->(q) {
where("title ILIKE :q OR body ILIKE :q", q: "%#{sanitize_sql_like(q)}%")
}
5. Callbacks (keep minimal)
before_validation :normalize_title
6. Instance methods
def publish!
update!(status: :published, published_at: Time.current)
end
private
def normalize_title
self.title = title&.strip
end
end
**Model ordering convention**: Associations, validations, enums, scopes, callbacks, class methods, instance methods, private methods.class Post < ApplicationRecord
1. Associations
belongs_to :user
belongs_to :category, optional: true
has_many :comments, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
has_one_attached :featured_image
2. Validations
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true
3. Enums
enum :status, { draft: 0, published: 1, archived: 2 }
4. Scopes
scope :published, -> { where(status: :published) }
scope :recent, -> { order(created_at: :desc) }
scope :by_category, ->(cat) { where(category: cat) }
scope :search, ->(q) {
where("title ILIKE :q OR body ILIKE :q", q: "%#{sanitize_sql_like(q)}%")
}
5. Callbacks (keep minimal)
before_validation :normalize_title
6. Instance methods
def publish!
update!(status: :published, published_at: Time.current)
end
private
def normalize_title
self.title = title&.strip
end
end
**模型代码排序约定**:关联定义、校验规则、枚举、scope、回调、类方法、实例方法、私有方法。Controllers
控制器
ruby
undefinedruby
undefinedapp/controllers/posts_controller.rb
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_post!, only: [:edit, :update, :destroy]
def index
@posts = Post.published
.includes(:user, :category)
.recent
.page(params[:page])
.per(20)
end
def show; end
def new
@post = current_user.posts.build
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
def edit; end
def update
if @post.update(post_params)
redirect_to @post, notice: "Post updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_url, notice: "Post deleted.", status: :see_other
end
private
def set_post
@post = Post.find(params[:id])
end
def authorize_post!
redirect_to posts_url, alert: "Not authorized." unless @post.user == current_user
end
def post_params
params.require(:post).permit(:title, :body, :category_id, :status, :featured_image, tag_ids: [])
end
end
undefinedclass PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_post!, only: [:edit, :update, :destroy]
def index
@posts = Post.published
.includes(:user, :category)
.recent
.page(params[:page])
.per(20)
end
def show; end
def new
@post = current_user.posts.build
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
def edit; end
def update
if @post.update(post_params)
redirect_to @post, notice: "Post updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_url, notice: "Post deleted.", status: :see_other
end
private
def set_post
@post = Post.find(params[:id])
end
def authorize_post!
redirect_to posts_url, alert: "Not authorized." unless @post.user == current_user
end
def post_params
params.require(:post).permit(:title, :body, :category_id, :status, :featured_image, tag_ids: [])
end
end
undefinedViews and Partials
视图和Partial
- Use partial shared between
_form.html.erbandnewedit - Use collection rendering: (auto-maps to
render @posts)_post.html.erb - Use locals:
render partial: "post", locals: { post: @post } - Use for page-specific titles
content_for :title - Use helpers for complex view logic (not inline ERB conditionals)
- 和
new页面共享editpartial_form.html.erb - 使用集合渲染:(自动映射到
render @posts)_post.html.erb - 使用locals传参:
render partial: "post", locals: { post: @post } - 页面专属标题使用定义
content_for :title - 复杂视图逻辑使用helper实现(不要在ERB中写内联条件判断)
Routing
路由
ruby
undefinedruby
undefinedconfig/routes.rb
config/routes.rb
Rails.application.routes.draw do
root "home#index"
Authentication
get "login", to: "sessions#new"
post "login", to: "sessions#create"
delete "logout", to: "sessions#destroy"
RESTful resources
resources :posts do
resources :comments, only: [:create, :destroy]
member do
post :publish
post :unpublish
end
end
resources :categories, only: [:index, :show] do
resources :posts, only: [:index]
end
Admin namespace
namespace :admin do
root "dashboard#index"
resources :users
resources :posts
end
API namespace
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
end
end
Health check
get "health", to: "health#show"
end
**Route conventions:**
- Use `resources` for standard CRUD (generates 7 RESTful routes)
- Use `only:` or `except:` to limit generated routes
- Use `member` for actions on a specific record, `collection` for actions on the set
- Nest resources only one level deep; use `shallow: true` for deeper nesting
- Namespace admin and API routes separatelyRails.application.routes.draw do
root "home#index"
Authentication
get "login", to: "sessions#new"
post "login", to: "sessions#create"
delete "logout", to: "sessions#destroy"
RESTful resources
resources :posts do
resources :comments, only: [:create, :destroy]
member do
post :publish
post :unpublish
end
end
resources :categories, only: [:index, :show] do
resources :posts, only: [:index]
end
Admin namespace
namespace :admin do
root "dashboard#index"
resources :users
resources :posts
end
API namespace
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
end
end
Health check
get "health", to: "health#show"
end
**路由约定**:
- 标准CRUD使用`resources`定义(自动生成7个RESTful路由)
- 使用`only:`或`except:`限制生成的路由范围
- 单条记录的操作使用`member`定义,集合级别的操作使用`collection`定义
- 资源嵌套最多一层,更深层级嵌套使用`shallow: true`
- 后台和API路由使用独立的命名空间Migrations
迁移
ruby
undefinedruby
undefineddb/migrate/20240115000000_create_posts.rb
db/migrate/20240115000000_create_posts.rb
class CreatePosts < ActiveRecord::Migration[7.1]
def change
create_table :posts do |t|
t.references :user, null: false, foreign_key: true
t.references :category, foreign_key: true
t.string :title, null: false
t.text :body, null: false
t.integer :status, default: 0, null: false
t.datetime :published_at
t.integer :comments_count, default: 0, null: false
t.timestamps
end
add_index :posts, [:user_id, :status]
add_index :posts, :published_atend
end
- Use `t.references` for foreign keys (adds index automatically)
- Use `t.timestamps` for `created_at` and `updated_at`
- Add composite indexes for common query patterns
- Use `comments_count` with `counter_cache: true` on the associationclass CreatePosts < ActiveRecord::Migration[7.1]
def change
create_table :posts do |t|
t.references :user, null: false, foreign_key: true
t.references :category, foreign_key: true
t.string :title, null: false
t.text :body, null: false
t.integer :status, default: 0, null: false
t.datetime :published_at
t.integer :comments_count, default: 0, null: false
t.timestamps
end
add_index :posts, [:user_id, :status]
add_index :posts, :published_atend
end
- 外键使用`t.references`定义(自动添加索引)
- 使用`t.timestamps`自动生成`created_at`和`updated_at`字段
- 常用查询模式添加联合索引
- `comments_count`配合关联上的`counter_cache: true`使用Service Objects
服务对象
ruby
undefinedruby
undefinedapp/services/application_service.rb
app/services/application_service.rb
class ApplicationService
def self.call(...)
new(...).call
end
end
class ApplicationService
def self.call(...)
new(...).call
end
end
app/services/posts/publish_service.rb
app/services/posts/publish_service.rb
module Posts
class PublishService < ApplicationService
def initialize(post:, user:)
@post = post
@user = user
end
def call
return ServiceResult.failure(["Not authorized"]) unless @user == @post.user
ActiveRecord::Base.transaction do
@post.update!(status: :published, published_at: Time.current)
notify_subscribers
end
ServiceResult.success(@post)
rescue ActiveRecord::RecordInvalid => e
ServiceResult.failure(e.record.errors.full_messages)
end
private
def notify_subscribers
PostMailer.published(@post).deliver_later
endend
end
- Use `self.call(...)` class method pattern for clean invocation
- Wrap multi-step operations in `ActiveRecord::Base.transaction`
- Return a result object (success/failure) instead of raising
- Namespace services by domain: `Posts::PublishService`, `Users::CreateService`module Posts
class PublishService < ApplicationService
def initialize(post:, user:)
@post = post
@user = user
end
def call
return ServiceResult.failure(["Not authorized"]) unless @user == @post.user
ActiveRecord::Base.transaction do
@post.update!(status: :published, published_at: Time.current)
notify_subscribers
end
ServiceResult.success(@post)
rescue ActiveRecord::RecordInvalid => e
ServiceResult.failure(e.record.errors.full_messages)
end
private
def notify_subscribers
PostMailer.published(@post).deliver_later
endend
end
- 使用`self.call(...)`类方法模式实现简洁调用
- 多步操作包裹在`ActiveRecord::Base.transaction`中
- 返回结果对象(成功/失败)而非抛出异常
- 服务按领域划分命名空间:`Posts::PublishService`、`Users::CreateService`Rails Commands
Rails命令
bash
undefinedbash
undefinedApplication
Application
rails new myapp --database=postgresql --css=tailwind
rails new myapp --api # API-only mode
rails server # Start dev server
rails console # Interactive console
rails routes # List all routes
rails new myapp --database=postgresql --css=tailwind
rails new myapp --api # API-only mode
rails server # Start dev server
rails console # Interactive console
rails routes # List all routes
Generators
Generators
rails generate model User name:string email:string
rails generate controller Posts index show new create
rails generate scaffold Article title:string body:text
rails generate migration AddStatusToPosts status:integer
rails generate model User name:string email:string
rails generate controller Posts index show new create
rails generate scaffold Article title:string body:text
rails generate migration AddStatusToPosts status:integer
Database
Database
rails db:create # Create database
rails db:migrate # Run pending migrations
rails db:rollback # Undo last migration
rails db:seed # Run seeds.rb
rails db:reset # Drop, create, migrate, seed
rails db:create # Create database
rails db:migrate # Run pending migrations
rails db:rollback # Undo last migration
rails db:seed # Run seeds.rb
rails db:reset # Drop, create, migrate, seed
Testing
Testing
rails test # Run all tests
rails test:models # Model tests only
rails test:system # System tests only
rails test # Run all tests
rails test:models # Model tests only
rails test:system # System tests only
Assets and dependencies
Assets and dependencies
bundle install # Install gems
rails assets:precompile # Compile assets for production
undefinedbundle install # Install gems
rails assets:precompile # Compile assets for production
undefinedTesting
测试
Minitest (Default)
Minitest (默认)
ruby
undefinedruby
undefinedtest/models/post_test.rb
test/models/post_test.rb
require "test_helper"
class PostTest < ActiveSupport::TestCase
def setup
@post = posts(:first_post)
end
test "valid post" do
assert @post.valid?
end
test "invalid without title" do
@post.title = nil
assert_not @post.valid?
assert_includes @post.errors[:title], "can't be blank"
end
test "publish! sets status and timestamp" do
@post.publish!
assert @post.published?
assert_not_nil @post.published_at
end
end
undefinedrequire "test_helper"
class PostTest < ActiveSupport::TestCase
def setup
@post = posts(:first_post)
end
test "valid post" do
assert @post.valid?
end
test "invalid without title" do
@post.title = nil
assert_not @post.valid?
assert_includes @post.errors[:title], "can't be blank"
end
test "publish! sets status and timestamp" do
@post.publish!
assert @post.published?
assert_not_nil @post.published_at
end
end
undefinedTesting Standards
测试标准
- Use fixtures (default) or for test data
factory_bot - Test validations, associations, scopes, and instance methods on models
- Test authentication, authorization, and response codes on controllers
- Use system tests (Capybara) for critical user flows
- Coverage target: >80% for models and services, >60% overall
- Test names describe behavior:
test "user cannot edit others' posts" - See references/patterns.md for controller and system test examples
- 使用fixtures(默认)或生成测试数据
factory_bot - 模型测试覆盖校验规则、关联、scope和实例方法
- 控制器测试覆盖认证、授权和响应码
- 核心用户流程使用系统测试(Capybara)覆盖
- 覆盖率目标:模型和服务>80%,整体>60%
- 测试用例名称描述行为:
test "user cannot edit others' posts" - 控制器和系统测试示例参考references/patterns.md
Dependencies
依赖
Core: , , , , , ,
rails ~> 7.1pgpumaredisturbo-railsstimulus-railsimportmap-railsAuth: (has_secure_password)
bcryptBackground:
sidekiqPagination: or
kaminaripagyDev/Test: , , , ,
debugcapybaraselenium-webdriverweb-consolerack-mini-profilerOptional: , , , ,
rspec-railsfactory_bot_railsfakershoulda-matcherswebmock核心依赖:, , , , , ,
rails ~> 7.1pgpumaredisturbo-railsstimulus-railsimportmap-rails认证:(实现has_secure_password)
bcrypt后台任务:
sidekiq分页:或
kaminaripagy开发/测试:, , , ,
debugcapybaraselenium-webdriverweb-consolerack-mini-profiler可选依赖:, , , ,
rspec-railsfactory_bot_railsfakershoulda-matcherswebmockBest Practices
最佳实践
Do
推荐
- Follow RESTful conventions for routes and controllers
- Use /
includeson every association accessed in viewspreload - Extract business logic to service objects
- Use scopes for all reusable query patterns
- Use for batch operations on large datasets
find_each - Use background jobs for email, notifications, and heavy processing
- Use fragment caching for expensive view partials
- Keep secrets in credentials or environment variables
- 路由和控制器遵循RESTful约定
- 视图中访问的每个关联都使用/
includes预加载preload - 业务逻辑抽取到服务对象中
- 所有可复用查询模式使用scope实现
- 大数据集批量操作使用
find_each - 邮件、通知和 heavy 计算使用后台任务处理
- 耗时的视图partial使用片段缓存
- 密钥存储在credentials或环境变量中
Don't
禁止
- Put business logic in controllers or views
- Use (mass-assignment vulnerability)
params.permit! - Use string interpolation in SQL queries
- Skip database indexes on foreign keys
- Use without pagination or limits
Model.all - Modify migrations after they have been applied to production
- Use /
rawwith user-provided datahtml_safe - Rely heavily on callbacks for business logic (use services)
- 控制器或视图中放置业务逻辑
- 使用(存在批量赋值漏洞)
params.permit! - SQL查询中使用字符串插值
- 外键省略数据库索引
- 无分页或限制使用
Model.all - 迁移已应用到生产环境后修改
- 用户提供的数据使用/
rawhtml_safe - 业务逻辑重度依赖回调(优先使用服务对象)
Advanced Topics
高级主题
For detailed code examples and advanced patterns, see:
- references/patterns.md -- ActiveRecord advanced patterns, Hotwire/Turbo, Action Cable, ActiveJob, API mode, deployment, and testing strategies
详细代码示例和高级模式参考:
- references/patterns.md -- ActiveRecord高级模式、Hotwire/Turbo、Action Cable、ActiveJob、API模式、部署和测试策略