从数组或对象(而不是哈希)生成路由时,如何将 `only_path: true` 和 `anchor:` 等选项传递给 `url_for`

Tyl*_*ick 6 ruby ruby-on-rails-5

我正在尝试更改一个函数 \xe2\x80\x94 ,该函数当前仅接受 URL(路径)字符串\xe2\x80\x94 以使其更加灵活,以便它也将接受与您相同的参数作为输入可以传递到url_for. 问题是,url_for返回一个完整的 URL,包括协议/主机,而我只想要路径部分...

\n\n

url_for有一个可爱的only_path: true选项,可以跳过添加协议/主机。只要您将其作为单个哈希的一部分传递给url_for

\n\n
main > app.url_for(controller: \'users\', action: \'index\', only_path: true)\n=> "/users"\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,当您将数组模型对象传递给url_for时,如何传递选项呢?

\n\n
main > app.url_for(user, only_path: true)\nArgumentError: wrong number of arguments (given 2, expected 0..1)\nfrom /gems/actionpack-5.1.6/lib/action_dispatch/routing/url_for.rb:166:in `url_for\'\n\nmain > app.url_for([user, :notification_preferences], only_path: true)\nArgumentError: wrong number of arguments (given 2, expected 0..1)\n/actionpack-5.1.6/lib/action_dispatch/routing/url_for.rb:166:in `url_for\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

你不能!显然,如果您传递散列以外的任何内容,则在语法上甚至不可能传递选项散列,因为的数量是0..1并且只需要一个参数:url_for(options = nil)

\n\n
\n\n

所以我的问题是,是否有一个 Rails 助手采用与url_for(包括anchor:)相同的选项但返回路径?

\n\n

我想添加一个,就像这个一样,使用URI.parse(path).path(可能是我现在要做的)不会太难......但这似乎效率低下,不优雅且不一致。

\n\n

效率低下且不优雅,因为它生成一个包含额外不需要的信息的字符串,然后必须对其进行解析,将其转换为结构化数据结构,然后将其转换不含不需要的信息的字符串。

\n\n

不一致的原因是:

\n\n
    \n
  1. Rails 包含_path每个_url路线助手的变体。为什么它没有内置的“路径”变体url_for(或者它有其他名称?)?

  2. \n
  3. 其他接受对象或数组\xe2\x80\x94(例如polymorphic_path\xe2\x80\x94)的路由助手也允许您传递选项:

  4. \n
\n\n

例子:

\n\n
main > app.polymorphic_url [user, :notification_preferences], anchor: \'foo\'\n=> "http://example.com/users/4/notification_preferences#foo"\n\nmain > app.polymorphic_path [user, :notification_preferences], anchor: \'foo\'\n=> "/users/4/notification_preferences#foo"\n\nmain > app.polymorphic_path user, anchor: \'foo\'\n=> "/users/4#foo"\n
Run Code Online (Sandbox Code Playgroud)\n\n

ActionDispatch::Routing::RouteSet实际上有一个path_for

\n\n
  # strategy for building urls to send to the client                        \n  PATH    = ->(options) { ActionDispatch::Http::URL.path_for(options) }     \n  UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }    \n\n  def path_for(options, route_name = nil)                                   \n    url_for(options, route_name, PATH)                                      \n  end                                                                       \n\n  # The +options+ argument must be a hash whose keys are *symbols*.         \n  def url_for(options, route_name = nil, url_strategy = UNKNOWN)            \n    options = default_url_options.merge options    \n    ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\x94 显然不是ActionDispatch::Routing::UrlFor。无论如何,ActionDispatch::Routing::RouteSet#path_for它埋藏得太深,对我没有帮助;我需要一个可以从控制器/视图调用的助手。

\n\n

那么,有什么好的解决方案可以优雅、与其他 Rails 路由助手一致并且相对高效(没有URI.parse)?

\n\n
\n\n

url_for更好的是,是否有任何原因不能简单地修改内置方法签名(在 Rails 的未来版本中,通过提交拉取请求)以允许主题(模型对象、数组或哈希)以及options要传入的任意数量的可选参数?

\n\n

最初的版本可能url_for是在 Ruby 有关键字参数之前编写的。但如今,做到这一点非常简单:接受一个“对象”加上任意数量的可选关键字选项:

\n\n
def url_for(object = nil, **options)\n  puts "object: #{object.inspect}"\n  puts "options: #{options.inspect}"\nend\n\nmain > url_for [\'user\', :notification_preferences], anchor: \'anchor\'\nobject: ["user", :notification_preferences]\noptions: {:anchor=>"anchor"}\n=> nil\n\nmain > url_for [\'user\', :notification_preferences], only_path: true\nobject: ["user", :notification_preferences]\noptions: {:only_path=>true}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们有什么理由不能/不应该将url_for方法签名更改为 吗url_for(object = nil, **options)

\n\n

您将如何修改url_for/full_url_for使其尽可能保持向后兼容,同时又让您使用数组+关键字选项来调用它?

\n

Tyl*_*ick 0

这是一个潜在的解决方案,它展示了我正在寻找的内容:

  def url_for(object = nil, **options)
    full_url_for(object, **options)
  end

  def full_url_for(object = nil, **options)
    path_or_url_for = ->(*args) {
      if options[:only_path]
        _routes.path_for(*args)
      else
        _routes.url_for( *args)
      end
    }
    polymorphic_path_or_url = ->(*args) {
      if options[:only_path]
        polymorphic_path(*args)
      else
        polymorphic_url( *args)
      end
    }

    case object
    when nil, Hash, ActionController::Parameters
      route_name = options.delete :use_route
      merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options)
      path_or_url_for.(merged_url_options, route_name)
    when String
      object
    when Symbol
      HelperMethodBuilder.url.handle_string_call self, object
    when Array
      components = object.dup
      polymorphic_path_or_url.(components, components.extract_options!)
    when Class
      HelperMethodBuilder.url.handle_class_call self, object
    else
      HelperMethodBuilder.url.handle_model_call self, object
    end
  end
Run Code Online (Sandbox Code Playgroud)

(与原始 Rails 版本的源代码比较)

我还没有尝试针对此代码运行 Rails 测试套件,因此它很可能错过了一些东西,但到目前为止它似乎适用于我的简单测试:

main > app.url_for(controller: 'users', action: 'index')
=> "http://example.com/users"

main > app.url_for(controller: 'users', action: 'index', only_path: true)
=> "/users"

main > app.url_for [user, :notification_preferences]
=> "http://example.com/users/4/notification_preferences"

main > app.url_for [user, :notification_preferences], only_path: true
=> "/users/4/notification_preferences"
Run Code Online (Sandbox Code Playgroud)

如果您发现其中有任何错误或有任何改进或可以想到更干净的方法来实现此目的,请告诉我。

请发布您可能拥有的任何改进版本作为答案!