Rails RESTful路由 #

一、REST架构概述 #

1.1 什么是REST #

REST(Representational State Transfer)是一种软件架构风格,用于设计网络应用。Rails完美支持RESTful设计。

1.2 REST原则 #

原则 说明
资源标识 每个资源有唯一URI
统一接口 使用标准HTTP方法
无状态 请求之间无依赖
分层系统 可分层架构
表现层分离 资源与表现分离

1.3 HTTP方法与CRUD #

HTTP方法 CRUD操作 说明
GET Read 获取资源
POST Create 创建资源
PUT/PATCH Update 更新资源
DELETE Delete 删除资源

二、资源路由详解 #

2.1 完整资源路由 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  resources :articles
end

2.2 生成的路由详解 #

路径助手 HTTP方法 路径 动作 用途
articles_path GET /articles index 文章列表
new_article_path GET /articles/new new 新建表单
articles_path POST /articles create 创建文章
article_path(id) GET /articles/:id show 文章详情
edit_article_path(id) GET /articles/:id/edit edit 编辑表单
article_path(id) PATCH /articles/:id update 更新文章
article_path(id) PUT /articles/:id update 更新文章
article_path(id) DELETE /articles/:id destroy 删除文章

2.3 控制器实现 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: %i[show edit update destroy]

  # GET /articles
  def index
    @articles = Article.all
  end

  # GET /articles/1
  def show
  end

  # GET /articles/new
  def new
    @article = Article.new
  end

  # GET /articles/1/edit
  def edit
  end

  # POST /articles
  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article, notice: '文章创建成功'
    else
      render :new, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /articles/1
  def update
    if @article.update(article_params)
      redirect_to @article, notice: '文章更新成功'
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /articles/1
  def destroy
    @article.destroy
    redirect_to articles_url, notice: '文章删除成功'
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body, :status)
  end
end

三、资源路由选项 #

3.1 限制动作 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 只生成指定动作
  resources :articles, only: [:index, :show]
  
  # 排除指定动作
  resources :comments, except: [:new, :edit]
  
  # 只读资源
  resources :pages, only: [:show]
end

3.2 自定义路径 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 自定义资源路径
  resources :articles, path: 'posts'
  # /posts => articles#index
  
  # 自定义路径助手前缀
  resources :articles, as: 'posts'
  # posts_path => /articles
  
  # 自定义控制器
  resources :articles, controller: 'posts'
  # 使用 PostsController
end

3.3 自定义成员和集合 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  resources :articles do
    # 成员路由(操作单个资源)
    member do
      get 'preview'      # /articles/:id/preview
      post 'publish'     # /articles/:id/publish
      post 'unpublish'   # /articles/:id/unpublish
    end
    
    # 集合路由(操作资源集合)
    collection do
      get 'search'       # /articles/search
      get 'popular'      # /articles/popular
      get 'archived'     # /articles/archived
    end
  end
end

3.4 路径助手 #

erb
<!-- 成员路由 -->
<%= link_to '预览', preview_article_path(@article) %>
<%= button_to '发布', publish_article_path(@article), method: :post %>

<!-- 集合路由 -->
<%= link_to '搜索', search_articles_path %>
<%= link_to '热门文章', popular_articles_path %>

四、嵌套资源 #

4.1 基本嵌套 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  resources :articles do
    resources :comments
  end
end

4.2 嵌套路由详解 #

路径助手 HTTP方法 路径 动作
article_comments_path(@article) GET /articles/:article_id/comments index
new_article_comment_path(@article) GET /articles/:article_id/comments/new new
article_comments_path(@article) POST /articles/:article_id/comments create
article_comment_path(@article, @comment) GET /articles/:article_id/comments/:id show
edit_article_comment_path(@article, @comment) GET /articles/:article_id/comments/:id/edit edit
article_comment_path(@article, @comment) PATCH /articles/:article_id/comments/:id update
article_comment_path(@article, @comment) DELETE /articles/:article_id/comments/:id destroy

4.3 控制器实现 #

ruby
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_article

  # GET /articles/:article_id/comments
  def index
    @comments = @article.comments
  end

  # POST /articles/:article_id/comments
  def create
    @comment = @article.comments.build(comment_params)

    if @comment.save
      redirect_to @article, notice: '评论创建成功'
    else
      redirect_to @article, alert: '评论创建失败'
    end
  end

  private

  def set_article
    @article = Article.find(params[:article_id])
  end

  def comment_params
    params.require(:comment).permit(:body, :author)
  end
end

4.4 浅层嵌套 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 浅层嵌套避免URL过深
  resources :articles do
    resources :comments, shallow: true
  end
end

# 生成的路由:
# GET    /articles/:article_id/comments      comments#index
# GET    /articles/:article_id/comments/new  comments#new
# POST   /articles/:article_id/comments      comments#create
# GET    /comments/:id                       comments#show
# GET    /comments/:id/edit                  comments#edit
# PATCH  /comments/:id                       comments#update
# DELETE /comments/:id                       comments#destroy

4.5 深度嵌套(不推荐) #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 避免超过一层嵌套
  resources :users do
    resources :articles do
      resources :comments
    end
  end
end

# 更好的做法:使用浅层嵌套
resources :users do
  resources :articles, only: [:index, :new, :create]
end

resources :articles, only: [:show, :edit, :update, :destroy] do
  resources :comments, shallow: true
end

五、命名空间 #

5.1 基本命名空间 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  namespace :admin do
    resources :users
    resources :articles
    root 'dashboard#index'
  end
end

5.2 生成的路由 #

路径助手 HTTP方法 路径 控制器
admin_users_path GET /admin/users admin/users#index
new_admin_user_path GET /admin/users/new admin/users#new
admin_users_path POST /admin/users admin/users#create
admin_user_path(id) GET /admin/users/:id admin/users#show
admin_root_path GET /admin admin/dashboard#index

5.3 目录结构 #

text
app/controllers/
├── application_controller.rb
└── admin/
    ├── application_controller.rb
    ├── dashboard_controller.rb
    ├── users_controller.rb
    └── articles_controller.rb

app/views/admin/
├── dashboard/
│   └── index.html.erb
├── users/
│   ├── index.html.erb
│   └── show.html.erb
└── articles/
    └── index.html.erb

5.4 控制器实现 #

ruby
# app/controllers/admin/application_controller.rb
module Admin
  class ApplicationController < ::ApplicationController
    before_action :require_admin
    
    private
    
    def require_admin
      redirect_to root_path unless current_user&.admin?
    end
  end
end

# app/controllers/admin/users_controller.rb
module Admin
  class UsersController < Admin::ApplicationController
    def index
      @users = User.all
    end
    
    def show
      @user = User.find(params[:id])
    end
  end
end

5.5 scope与namespace #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # namespace:路径、模块、助手前缀都改变
  namespace :admin do
    resources :users
  end
  # /admin/users => Admin::UsersController
  # admin_users_path
  
  # scope module:只改变模块
  scope module: 'admin' do
    resources :users
  end
  # /users => Admin::UsersController
  # users_path
  
  # scope path:只改变路径
  scope path: '/admin' do
    resources :users
  end
  # /admin/users => UsersController
  # users_path
  
  # scope as:只改变助手前缀
  scope as: 'admin' do
    resources :users
  end
  # /users => UsersController
  # admin_users_path
end

六、API路由设计 #

6.1 API命名空间 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :articles
      resources :users, only: [:index, :show]
      post 'login', to: 'authentication#login'
    end
    
    namespace :v2 do
      resources :articles
      resources :users
    end
  end
end

6.2 API控制器 #

ruby
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < ApplicationController
      skip_before_action :verify_authenticity_token
      
      def index
        @articles = Article.all
        render json: @articles
      end
      
      def show
        @article = Article.find(params[:id])
        render json: @article
      end
      
      def create
        @article = Article.new(article_params)
        
        if @article.save
          render json: @article, status: :created
        else
          render json: { errors: @article.errors }, status: :unprocessable_entity
        end
      end
      
      private
      
      def article_params
        params.require(:article).permit(:title, :body)
      end
    end
  end
end

6.3 API路由约束 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 子域名API
  constraints subdomain: 'api' do
    namespace :api, path: '/' do
      namespace :v1 do
        resources :articles
      end
    end
  end
  
  # 格式约束
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :articles
    end
  end
end

七、路由Concerns #

7.1 定义Concerns #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 定义可复用的路由模块
  concern :commentable do
    resources :comments
  end
  
  concern :likable do
    resources :likes, only: [:create, :destroy]
  end
  
  # 使用concerns
  resources :articles, concerns: [:commentable, :likable]
  resources :photos, concerns: [:commentable, :likable]
end

7.2 Concerns最佳实践 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  concern :paginatable do
    get 'page/:page', action: :index, on: :collection
  end
  
  concern :searchable do
    collection do
      get 'search'
    end
  end
  
  resources :articles, concerns: [:paginatable, :searchable]
  resources :users, concerns: [:paginatable, :searchable]
end

八、路由重定向 #

8.1 基本重定向 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 简单重定向
  get 'old-path', to: redirect('/new-path')
  
  # 重定向到命名路由
  get 'home', to: redirect(root_path)
  
  # 外部重定向
  get 'external', to: redirect('https://example.com')
end

8.2 动态重定向 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 使用块进行动态重定向
  get 'users/:name', to: redirect { |params|
    "/profiles/#{params[:name]}"
  }
  
  # 带状态码
  get 'old-page', to: redirect('/new-page', status: 301)
end

九、路由最佳实践 #

9.1 路由组织 #

ruby
# config/routes.rb
Rails.application.routes.draw do
  # 1. 根路由
  root 'home#index'
  
  # 2. 公开资源
  resources :articles, only: [:index, :show]
  resources :categories, only: [:index, :show]
  
  # 3. 用户资源
  resources :users, only: [:show] do
    resources :articles, only: [:index]
  end
  
  # 4. 认证路由
  get 'login', to: 'sessions#new'
  post 'login', to: 'sessions#create'
  delete 'logout', to: 'sessions#destroy'
  
  # 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
end

9.2 RESTful设计原则 #

原则 说明
使用资源名词 /articles 而非 /article_list
使用HTTP方法 GET/POST/PUT/DELETE 而非 /delete_article
嵌套不超过一层 避免深层嵌套
使用命名空间 组织相关路由
保持一致性 统一命名规范

9.3 路由命名规范 #

ruby
# 推荐
resources :articles
resources :users

# 不推荐
resources :article  # 应该用复数
resources :User     # 应该用小写

十、总结 #

10.1 核心要点 #

要点 说明
RESTful 使用资源和HTTP方法
资源路由 resources :articles
嵌套资源 一层嵌套最佳
命名空间 组织相关路由
API设计 版本控制和格式约束

10.2 下一步 #

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

最后更新:2026-03-28