如何有条件地在 Rails 中包含路由参数?

How to conditionally include route parameter in Rails?

我正在尝试允许一个资源 Site 有条件地拥有一个父资源 Organization

这是我目前拥有的:

resources :site do
  resources :posts do
    resources :comments
  end
end

这会导致像

这样的路径
/sites/1
/sites/1/edit
/sites/1/posts
/sites/1/posts/123/comments
# etc. etc.

我希望能够拥有像

这样的路径
/parent_id/sites/1
/parent_id/sites/1/edit
/parent_id/sites/1/posts
/parent_id/sites/1/posts/123/comments

仅当站点属于组织

我也不想更改已在我的网站上使用的每个路径助手(实际上有数百个地方)。

这可能吗?

这是我尝试过的方法:

scope "(:organization_id)/", defaults: { organization_id: nil } do
  resources :site
end

# ...

# in application_controller.rb
def default_url_options(options = {})
  options.merge(organization_id: @site.organization_id) if @site&.organization_id
end

但这没有用。 organization_id 没有设置。

# in my views
link_to "My Site", site_path(site)
# results in /sites/1 instead of /321/sites/1

我也试过在路由约束中设置 organization_id,但效果不佳。

在您的路线中添加另一个块,并围绕它提供公司资源:

resources :companies do
  resources :site do
    resources :posts do
      resources :comments
    end
  end
end

resources :site do
  resources :posts do
    resources :comments
  end
end

现在您可以像这样为您的链接创建一个助手:

# sites_helper.rb
module SitesHelper
  def link_to_site(text, site)
    if site.company
      link_to text, company_site_path(site.company, site)
    else
      link_to text, site_path(site)      
    end
  end
end

然后像这样在您的视图中使用它:

<%= link_to_site("Text of the link", variable_name) %>

注意参数中的 variable_name。它可以是站点或@site,具体取决于您的代码。在循环中它可能是 site 但在显示页面上我猜它会是 @site.

您非常接近当前的方法。问题似乎是路由 scope 上的 defaults 优先于 default_url_options,因此您每次都会得到一个零组织 ID。

试试看,只是:

# in routes.rb
scope "(:organization_id)" do
  resources :site
  ...
end

# in application_controller.rb
def default_url_options(options = {})
  # either nil, or a valid organization ID
  options.merge(organization_id: @site&.organization_id)
end

我最终写了一个猴子补丁来覆盖为我所有相关路径生成的动态方法。我使用了一个自定义路由选项,infer_organization_from_site,我在动态生成路由时寻找它。如果设置了该选项,我将添加 site.organization 作为助手调用的第一个参数。

# in config/routes.rb

scope "(:organization_id)", infer_organization_from_site: true do
  resources :sites do
    resources :posts
    # etc.
  end
end 

# in an initializer in config/initializers/

module ActionDispatch
  module Routing
    # :stopdoc:
    class RouteSet
      class NamedRouteCollection

        private

        # Overridden actionpack-4.2.11/lib/action_dispatch/routing/route_set.rb
        # Before this patch, we couldn't add the organization in the URL when
        # we used eg. site_path(site) without changing it to site_path(organization, site).
        # This patch allows us to keep site_path(site), and along with the proper
        # optional parameter in the routes, allows us to not include the organization
        # for sites that don't have one.
        def define_url_helper(mod, route, name, opts, route_key, url_strategy)
          helper = UrlHelper.create(route, opts, route_key, url_strategy)
          mod.module_eval do
            define_method(name) do |*args|
              options = nil
              options = args.pop if args.last.is_a? Hash
              if opts[:infer_organization_from_site]
                args.prepend args.first.try(:organization)
              end
              helper.call self, args, options
            end
          end
        end
      end
    end
  end
end