Rails路由约束 #

一、约束概述 #

1.1 什么是路由约束 #

路由约束用于限制路由匹配的条件,只有满足约束条件的请求才会匹配该路由。

1.2 约束类型 #

类型 说明
参数约束 限制动态参数格式
请求约束 限制请求属性
自定义约束 自定义约束类

二、参数约束 #

2.1 基本参数约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 只匹配数字ID
  get 'articles/:id', to: 'articles#show', constraints: { id: /\d+/ }
  
  # 只匹配字母
  get 'users/:name', to: 'users#show', constraints: { name: /[a-z]+/ }
  
  # UUID格式
  get 'items/:id', to: 'items#show', constraints: { id: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i }
end

2.2 多参数约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 多个参数约束
  get 'articles/:year/:month', to: 'articles#archive',
    constraints: {
      year: /\d{4}/,
      month: /\d{2}/
    }
  
  # 日期约束
  get 'posts/:date', to: 'posts#by_date',
    constraints: { date: /\d{4}-\d{2}-\d{2}/ }
end

2.3 格式约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 限制响应格式
  resources :articles, constraints: { format: /(html|json|xml)/ }
  
  # 只允许JSON格式
  namespace :api, constraints: { format: :json } do
    resources :articles
  end
end

三、请求约束 #

3.1 子域名约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 匹配特定子域名
  constraints subdomain: 'admin' do
    resources :users
    resources :articles
  end
  
  # 匹配任意子域名
  constraints subdomain: /.+/ do
    resources :tenants
  end
  
  # 排除特定子域名
  constraints subdomain: /^(?!api|admin).*/ do
    resources :pages
  end
end

3.2 域名约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 匹配特定域名
  constraints domain: 'example.com' do
    root 'home#index'
  end
  
  # 匹配多个域名
  constraints domain: /(example|test)\.com/ do
    root 'home#index'
  end
end

3.3 IP约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 单个IP
  constraints ip: '192.168.1.100' do
    get 'admin', to: 'admin#index'
  end
  
  # 多个IP
  constraints ip: %w[192.168.1.100 192.168.1.101] do
    get 'admin', to: 'admin#index'
  end
  
  # IP范围
  constraints ip: '192.168.1.0/24' do
    get 'internal', to: 'internal#index'
  end
end

3.4 HTTP方法约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 通过via限制HTTP方法
  match 'articles', to: 'articles#index', via: :get
  match 'articles', to: 'articles#create', via: :post
  
  # 多个HTTP方法
  match 'articles', to: 'articles#handle', via: [:get, :post]
end

3.5 请求头约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # User-Agent约束
  constraints user_agent: /Mobile/ do
    get 'mobile', to: 'mobile#index'
  end
  
  # 自定义请求头
  constraints ->(req) { req.headers['X-API-Key'].present? } do
    namespace :api do
      resources :articles
    end
  end
end

四、自定义约束类 #

4.1 创建约束类 #

ruby
# lib/constraints/admin_constraint.rb
class AdminConstraint
  def matches?(request)
    # 返回true或false
    request.session[:user_id].present? &&
      User.find_by(id: request.session[:user_id])&.admin?
  end
end

# lib/constraints/api_constraint.rb
class ApiConstraint
  def initialize(options)
    @version = options[:version]
    @default = options[:default]
  end

  def matches?(request)
    @default || request.headers['Accept'].include?("application/vnd.myapp.v#{@version}")
  end
end

4.2 使用约束类 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 使用自定义约束
  constraints AdminConstraint.new do
    namespace :admin do
      resources :users
      resources :articles
    end
  end
  
  # API版本约束
  namespace :api, defaults: { format: :json } do
    scope constraints: ApiConstraint.new(version: 1, default: true) do
      resources :articles
    end
  end
end

4.3 约束类最佳实践 #

ruby
# lib/constraints/authenticated_constraint.rb
class AuthenticatedConstraint
  def initialize(role: nil)
    @role = role
  end

  def matches?(request)
    user = current_user(request)
    return false unless user
    
    @role.nil? || user.has_role?(@role)
  end

  private

  def current_user(request)
    User.find_by(id: request.session[:user_id])
  end
end

# config/routes.rb
Rails.application.routes.draw do
  constraints AuthenticatedConstraint.new do
    resources :dashboard
  end
  
  constraints AuthenticatedConstraint.new(role: :admin) do
    namespace :admin do
      resources :users
    end
  end
end

五、Lambda约束 #

5.1 基本Lambda约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 使用lambda作为约束
  constraints ->(request) { request.env['HTTP_X_API_KEY'] == 'secret' } do
    namespace :api do
      resources :articles
    end
  end
end

5.2 复杂Lambda约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 检查多个条件
  constraints lambda { |req|
    req.session[:user_id].present? &&
    User.find(req.session[:user_id]).premium?
  } do
    resources :premium_features
  end
  
  # 基于时间的约束
  constraints lambda { |req|
    Time.now.hour >= 9 && Time.now.hour < 18
  } do
    get 'support', to: 'support#index'
  end
end

六、组合约束 #

6.1 多重约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 同时满足多个约束
  constraints subdomain: 'api', format: :json do
    resources :articles
  end
  
  # 使用约束类和参数约束
  constraints AdminConstraint.new, id: /\d+/ do
    resources :users
  end
end

6.2 约束优先级 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 更具体的约束应该放在前面
  constraints subdomain: 'admin' do
    resources :users
  end
  
  constraints subdomain: 'api' do
    resources :articles
  end
  
  # 通用路由放在最后
  resources :pages
end

七、动态约束 #

7.1 基于数据库的约束 #

ruby
# lib/constraints/tenant_constraint.rb
class TenantConstraint
  def matches?(request)
    subdomain = request.subdomain
    return false if subdomain.blank?
    
    Tenant.exists?(subdomain: subdomain)
  end
end

# config/routes.rb
Rails.application.routes.draw do
  constraints TenantConstraint.new do
    resources :dashboard
    resources :articles
  end
end

7.2 基于配置的约束 #

ruby
# lib/constraints/feature_constraint.rb
class FeatureConstraint
  def initialize(feature)
    @feature = feature
  end

  def matches?(request)
    Rails.configuration.features[@feature]
  end
end

# config/routes.rb
Rails.application.routes.draw do
  constraints FeatureConstraint.new(:new_ui) do
    get 'dashboard', to: 'dashboard_v2#index'
  end
end

八、路由通配符 #

8.1 通配符片段 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 匹配任意路径
  get 'pages/*path', to: 'pages#show'
  
  # 示例:
  # /pages/about/company => params[:path] = 'about/company'
  # /pages/help/faq      => params[:path] = 'help/faq'
end

8.2 通配符约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 约束通配符内容
  get 'files/*path', to: 'files#show',
    constraints: { path: /[a-z0-9\/_\-]+/i }
end

九、重定向约束 #

9.1 条件重定向 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 基于约束的重定向
  constraints subdomain: 'old' do
    get '*path', to: redirect { |params, request|
      "https://new.example.com/#{params[:path]}"
    }
  end
end

十、约束调试 #

10.1 查看路由 #

bash
# 查看所有路由
rails routes

# 过滤路由
rails routes | grep admin

# 扩展格式
rails routes -E

10.2 测试约束 #

ruby
# test/integration/routes_test.rb
require 'test_helper'

class RoutesTest < ActionDispatch::IntegrationTest
  test 'admin routes require admin user' do
    get admin_users_path
    assert_response :redirect
    
    sign_in users(:admin)
    get admin_users_path
    assert_response :success
  end
  
  test 'API routes require correct format' do
    get api_articles_path, headers: { 'Accept' => 'application/json' }
    assert_response :success
    
    get api_articles_path, headers: { 'Accept' => 'text/html' }
    assert_response :not_acceptable
  end
end

十一、最佳实践 #

11.1 约束组织 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 1. 根路由
  root 'home#index'
  
  # 2. 公开路由
  resources :articles, only: [:index, :show]
  
  # 3. 认证路由
  constraints AuthenticatedConstraint.new do
    resources :dashboard
  end
  
  # 4. 管理路由
  constraints AdminConstraint.new do
    namespace :admin do
      resources :users
    end
  end
  
  # 5. API路由
  constraints subdomain: 'api' do
    namespace :api do
      resources :articles
    end
  end
end

11.2 约束命名规范 #

ruby
# 推荐
class AdminConstraint; end
class AuthenticatedConstraint; end
class ApiConstraint; end

# 不推荐
class Admin; end        # 太通用
class CheckAdmin; end   # 应该用名词

11.3 性能考虑 #

ruby
# 避免在约束中进行复杂查询
class BadConstraint
  def matches?(request)
    # 不推荐:每次请求都查询数据库
    User.where(active: true).count > 100
  end
end

class GoodConstraint
  def matches?(request)
    # 推荐:使用缓存
    Rails.cache.fetch('active_users_count', expires_in: 5.minutes) do
      User.where(active: true).count
    end > 100
  end
end

十二、总结 #

12.1 核心要点 #

要点 说明
参数约束 限制动态参数格式
请求约束 限制子域名、IP等
自定义约束 创建约束类
Lambda约束 快速定义约束
组合约束 多重约束条件

12.2 下一步 #

现在你已经掌握了路由约束,接下来让我们学习 路由高级特性,深入了解路由的高级功能!

最后更新:2026-03-28