如何在 Rails 中定义包含动态组织名称的路由

How to define routes that include a dynamic organization name in Rails

我正在构建一个 Rails 5.2 SaaS 应用程序,它允许用户属于许多 "organizations"。用户将只能看到他们当前活跃的组织的内容。

我开始使用子域,但经过更多研究后 decided to avoid them for now

我的新方法(向用户明确他们正在使用的组织、支持共享链接、浏览器历史记录等)是在路径中嵌入组织名称。例如:

https://app.example.com/foo/posts   # Posts for org "foo"
https://app.example.com/foo/posts/7 # Post for org "foo"
https://app.example.com/bar/posts   # Posts for org "bar"
https://app.example.com/settings    # General account settings
https://app.example.com/signin      # Sign in

我的问题是如何使用 Rails 路由来做到这一点?我试过使用动态范围:

scope ':org' do
  resources :posts
end

导致如下错误:

No route matches {:action=>"show", :controller=>"posts", :org=>#<Organization id: 1, name: "My Organization", ...}, missing required keys: [:id]

对于代码:

# layouts/application.html.erb
<%= link_to post, post, class: 'dropdown-item' %>

关于如何配置路由以支持此用例的任何建议?

使用资源宏:

Rails.application.routes.draw do
  resources :posts, except: [:new, :create]
  resources :organizations, path: '/', only: [] do
    resources :posts, module: :organizations, only: [:new, :index, :create]
  end
end

$ rails routes:

               Prefix Verb   URI Pattern                           Controller#Action
                posts GET    /posts(.:format)                      posts#index
            edit_post GET    /posts/:id/edit(.:format)             posts#edit
                 post GET    /posts/:id(.:format)                  posts#show
                      PATCH  /posts/:id(.:format)                  posts#update
                      PUT    /posts/:id(.:format)                  posts#update
                      DELETE /posts/:id(.:format)                  posts#destroy
   organization_posts GET    /:organization_id/posts(.:format)     organizations/posts#index
                      POST   /:organization_id/posts(.:format)     organizations/posts#create
new_organization_post GET    /:organization_id/posts/new(.:format) organizations/posts#new

通过使用 module: 选项,您可以为嵌套上下文设置单独的控制器:

# app/controllers/organizations/posts_controller.rb
class Organizations::PostsController < ApplicationController

  before_action :set_organization!

  # Index for posts belonging to a specific organization
  # GET    /:organization_id/posts
  def index
    @posts = @organization.posts
  end

  # GET    /:organization_id/posts/new
  def new
    @post = @organization.posts.new
  end

  # POST   /:organization_id/posts
  def create
    @post = Post.new(post_params)

    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  private
    def set_organization!
      @organization = Organization.includes(:posts)
                                  .find_by!(name: params[:organization_id])
    end 

    def post_params
      params.require(:post).permit(:title)
    end
end

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # Index for posts belonging to all organizations
  # GET /posts
  def index
    @posts = Post.all
  end

  # GET /posts/1
  def show
  end

  # GET /posts/1/edit
  def edit
  end

  # PATCH/PUT /posts/1
  def update
    if @post.update(post_params)
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /posts/1
  def destroy
    @post.destroy
    redirect_to posts_url, notice: 'Post was successfully destroyed.'
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def post_params
      params.require(:post).permit(:title)
    end
end

但是,在创建以动态段开头的路由时应注意 - 路由按其定义的顺序具有优先级,以动态段开头的路由将 "greedy" 并吞并其他路由如果没有先定义它们。