【问题标题】:How to conditionally include route parameter in Rails?如何有条件地在 Rails 中包含路由参数?
【发布时间】:2019-07-30 12:37:37
【问题描述】:

我正在尝试允许一个资源 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,但效果不佳。

【问题讨论】:

    标签: ruby-on-rails rails-routing


    【解决方案1】:

    在您的路线中添加另一个区块,并包裹公司资源:

    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。

    【讨论】:

    • 谢谢,这在最简单的情况下确实有效,但不幸的是,正如我在问题中所述,替换我网站上的每个链接助手并不是我愿意做的事情。每个“站点”都有几十个页面,每个页面都链接到其他页面,所以我必须为每个 site_X_path 创建这种帮助程序(如果我想能够使用 site_X_url,则需要 x2。
    • 在那种情况下,我认为您没有考虑好如何组织您的应用程序,或者它增长得太多以至于您没有其他选择,只能修改每个链接。顺便说一句,通过查找和替换,修改链接的工作量并不大。
    【解决方案2】:

    您目前的方法非常接近。问题似乎是路由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
    

    【讨论】:

    • 感谢您的回答。我认为我们现在更接近了,但我仍然遇到例如问题。 site_path(site)。 Rails 假设第一个参数是organization_id,所以传递给default_url_optionsoptions 将包含{ organization_id: site.id, id: nil }。还有什么想法吗?
    • @you786:这很令人惊讶,因为变量仍然设置为可选:(:organisation_id)。您可以尝试 a) 从site 合并idorganization_id,如果这始终是一个对象; b) 覆盖site_path 而不是依赖default_url_options
    【解决方案3】:

    我最终编写了一个猴子补丁,覆盖了为所有相关路径生成的动态方法。我使用了一个自定义路由选项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
    

    【讨论】:

      猜你喜欢
      • 2013-10-14
      • 2011-05-15
      • 2012-12-09
      • 2021-02-18
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-20
      相关资源
      最近更新 更多