Rails路由高级特性 #
一、路由Concerns #
1.1 什么是Concerns #
Concerns是可复用的路由模块,可以在多个资源之间共享路由配置。
1.2 定义Concerns #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 定义可复用的路由模块
concern :commentable do
resources :comments, only: [:index, :create, :destroy]
end
concern :likable do
resources :likes, only: [:create, :destroy]
end
concern :searchable do
collection do
get 'search'
end
end
concern :paginatable do
get 'page/:page', action: :index, on: :collection
end
end
1.3 使用Concerns #
ruby
# config/routes.rb
Rails.application.routes.draw do
concern :commentable do
resources :comments, only: [:create, :destroy]
end
concern :likable do
resources :likes, only: [:create, :destroy]
end
# 使用多个concerns
resources :articles, concerns: [:commentable, :likable]
resources :photos, concerns: [:commentable, :likable]
resources :videos, concerns: [:commentable]
end
1.4 Concerns嵌套 #
ruby
# config/routes.rb
Rails.application.routes.draw do
concern :commentable do
resources :comments do
concerns :likable
end
end
resources :articles, concerns: :commentable
end
二、挂载引擎 #
2.1 挂载Rack应用 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 挂载Sidekiq Web界面
mount Sidekiq::Web => '/sidekiq'
# 挂载Grape API
mount API => '/api'
# 挂载Rails引擎
mount Blazer::Engine => '/blazer'
end
2.2 挂载带约束 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 只允许管理员访问
constraints AdminConstraint.new do
mount Sidekiq::Web => '/sidekiq'
mount Blazer::Engine => '/blazer'
end
# 子域名约束
constraints subdomain: 'admin' do
mount RailsAdmin::Engine => '/admin'
end
end
2.3 常见挂载示例 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# Sidekiq监控
mount Sidekiq::Web => '/sidekiq'
# 管理后台
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
# API文档
mount Rswag::Api::Engine => '/api-docs'
mount Rswag::Ui::Engine => '/api-docs'
# 性能监控
mount Bullet::Engine => '/bullet' if Rails.env.development?
# 健康检查
mount OkComputer::Engine => '/health'
end
三、路由优先级 #
3.1 路由匹配顺序 #
Rails按照定义顺序匹配路由,第一个匹配的路由会被使用。
ruby
# config/routes.rb
Rails.application.routes.draw do
# 更具体的路由应该放在前面
# 1. 特定路由
get 'articles/popular', to: 'articles#popular'
# 2. 动态路由
get 'articles/:id', to: 'articles#show'
# 3. 通配符路由
get 'articles/*path', to: 'articles#catch_all'
end
3.2 路由冲突解决 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 错误:动态路由会先匹配
# get 'articles/:id', to: 'articles#show'
# get 'articles/popular', to: 'articles#popular'
# 正确:特定路由在前
get 'articles/popular', to: 'articles#popular'
get 'articles/:id', to: 'articles#show', constraints: { id: /\d+/ }
end
四、路由格式 #
4.1 默认格式 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 设置默认格式
resources :articles, defaults: { format: :json }
# 命名空间默认格式
namespace :api, defaults: { format: :json } do
resources :articles
end
end
4.2 格式约束 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 只允许特定格式
resources :articles, constraints: { format: /(html|json|xml)/ }
# 禁止格式后缀
resources :articles, format: false
end
4.3 格式变体 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 支持多种格式
resources :articles do
member do
get 'download', constraints: { format: /(pdf|doc)/ }
end
end
end
五、路由优化 #
5.1 路由缓存 #
ruby
# config/environments/production.rb
Rails.application.configure do
# 启用路由缓存
config.action_dispatch.railties_order_engine_routes = true
end
5.2 减少路由数量 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 不推荐:生成所有路由
resources :articles
# 推荐:只生成需要的路由
resources :articles, only: [:index, :show]
# 使用concerns复用路由
concern :readable do
get 'read', on: :member
end
resources :articles, concerns: :readable
resources :books, concerns: :readable
end
5.3 延迟加载 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 开发环境延迟加载
if Rails.env.development?
mount LetterOpenerWeb::Engine => '/letter_opener'
end
end
六、路由助手方法 #
6.1 自定义助手方法 #
ruby
# app/helpers/route_helper.rb
module RouteHelper
def article_permalink(article)
article_path(article, anchor: "comment-#{article.last_comment_id}")
end
def user_profile_path(user)
user_path(user.username.downcase)
end
end
6.2 路由助手选项 #
erb
<!-- 路径助手选项 -->
<%= link_to '文章', article_path(@article, anchor: 'comments') %>
<%= link_to '文章', article_path(@article, format: :json) %>
<%= link_to '文章', article_path(@article, only_path: false) %>
<!-- URL参数 -->
<%= link_to '搜索', articles_path(q: 'rails', page: 2) %>
七、路由国际化 #
7.1 多语言路由 #
ruby
# config/routes.rb
Rails.application.routes.draw do
scope '(:locale)', locale: /en|zh|ja/ do
resources :articles
root 'home#index'
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_locale
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def default_url_options
{ locale: I18n.locale }
end
end
7.2 本地化路径 #
ruby
# config/routes.rb
Rails.application.routes.draw do
localized do
get 'about', to: 'pages#about', as: :about
get 'contact', to: 'pages#contact', as: :contact
end
end
# config/locales/routes/en.yml
en:
routes:
about: 'about'
contact: 'contact'
# config/locales/routes/zh.yml
zh:
routes:
about: 'guanyu'
contact: 'lianxi'
八、路由测试 #
8.1 路由测试示例 #
ruby
# test/integration/routes_test.rb
require 'test_helper'
class RoutesTest < ActionDispatch::IntegrationTest
test 'should route to articles' do
assert_routing '/articles', controller: 'articles', action: 'index'
end
test 'should route to article' do
assert_routing '/articles/1', controller: 'articles', action: 'show', id: '1'
end
test 'should route to new article' do
assert_routing '/articles/new', controller: 'articles', action: 'new'
end
test 'should route nested resource' do
assert_routing '/articles/1/comments',
controller: 'comments', action: 'index', article_id: '1'
end
test 'should recognize admin routes' do
assert_routing '/admin/users',
controller: 'admin/users', action: 'index'
end
end
8.2 测试助手方法 #
ruby
# test/helpers/routes_helper_test.rb
require 'test_helper'
class RoutesHelperTest < ActionView::TestCase
test 'should generate article permalink' do
article = articles(:one)
article.stubs(last_comment_id: 5)
expected = "/articles/#{article.id}#comment-5"
assert_equal expected, article_permalink(article)
end
end
九、路由调试 #
9.1 路由调试工具 #
bash
# 查看所有路由
rails routes
# 过滤路由
rails routes | grep articles
# 扩展格式
rails routes -E
# 只显示特定控制器
rails routes -c ArticlesController
# 只显示特定路径
rails routes -g articles
9.2 控制台调试 #
ruby
# Rails控制台
rails console
# 检查路由助手
app.articles_path
# => "/articles"
app.article_path(1)
# => "/articles/1"
# 检查URL生成
Rails.application.routes.url_helpers.articles_path
# => "/articles"
9.3 视图调试 #
erb
<!-- 显示当前路由信息 -->
<%= debug(params) %>
<!-- 显示所有路由 -->
<% Rails.application.routes.routes.each do |route| %>
<%= route.path.spec.to_s %>
<% end %>
十、高级路由模式 #
10.1 多租户路由 #
ruby
# config/routes.rb
Rails.application.routes.draw do
constraints subdomain: /.+/ do
get '/', to: 'tenants#show'
resources :articles
end
end
# app/controllers/tenants_controller.rb
class TenantsController < ApplicationController
def show
@tenant = Tenant.find_by(subdomain: request.subdomain)
render :not_found unless @tenant
end
end
10.2 API版本控制 #
ruby
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :articles
end
namespace :v2 do
resources :articles
end
end
# 或使用约束
scope module: :api do
scope module: :v1, constraints: ApiConstraint.new(version: 1) do
resources :articles
end
scope module: :v2, constraints: ApiConstraint.new(version: 2) do
resources :articles
end
end
end
10.3 动态路由 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 从数据库加载路由
Page.all.each do |page|
get page.slug, to: 'pages#show', defaults: { page_id: page.id }
end
end
# 更好的做法:使用约束
get '*slug', to: 'pages#show', constraints: PageConstraint.new
# lib/constraints/page_constraint.rb
class PageConstraint
def matches?(request)
Page.exists?(slug: request.params[:slug])
end
end
十一、最佳实践 #
11.1 路由组织 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 1. 根路由
root 'home#index'
# 2. 公开资源
resources :articles, only: [:index, :show]
# 3. 认证路由
devise_for :users
# 4. 用户资源
resources :users, only: [:show] do
resources :articles, only: [:index]
end
# 5. 管理后台
namespace :admin do
root 'dashboard#index'
resources :users
resources :articles
end
# 6. API路由
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :articles
end
end
# 7. 挂载引擎
mount Sidekiq::Web => '/sidekiq'
end
11.2 性能优化 #
| 优化点 | 说明 |
|---|---|
| 减少路由数量 | 只生成需要的路由 |
| 使用concerns | 复用路由配置 |
| 路由缓存 | 生产环境启用缓存 |
| 避免深层嵌套 | 限制嵌套层级 |
| 使用约束 | 减少不必要的匹配 |
11.3 安全考虑 #
ruby
# config/routes.rb
Rails.application.routes.draw do
# 限制管理路由访问
constraints AdminConstraint.new do
namespace :admin do
resources :users
end
end
# 限制API访问
constraints ApiConstraint.new do
namespace :api do
resources :articles
end
end
end
十二、总结 #
12.1 核心要点 #
| 要点 | 说明 |
|---|---|
| Concerns | 可复用路由模块 |
| 挂载引擎 | 集成第三方应用 |
| 路由优先级 | 定义顺序影响匹配 |
| 路由优化 | 减少数量、使用缓存 |
| 国际化 | 多语言路由支持 |
12.2 下一步 #
现在你已经掌握了路由高级特性,接下来让我们学习 控制器基础,深入了解Rails的控制器系统!
最后更新:2026-03-28