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

Posted

技术标签:

【中文标题】从数组或对象(而不是哈希)生成路由时,如何将“only_path:true”和“anchor:”等选项传递给“url_for”【英文标题】:How to pass options like `only_path: true` and `anchor:` to `url_for` when generating a route from an array or object (instead of from a hash) 【发布时间】:2019-06-02 18:50:17 【问题描述】:

我正在尝试更改一个函数——它目前只接受一个 URL(路径)string——使它更灵活,以便它也可以接受你可以接受的相同参数作为输入传递给url_for。问题是,url_for 返回一个完整的 URL,包括协议/主机,我只想要路径部分...

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

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

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

main > app.url_for(user, only_path: true)
ArgumentError: wrong number of arguments (given 2, expected 0..1)
from /gems/actionpack-5.1.6/lib/action_dispatch/routing/url_for.rb:166:in `url_for'

main > app.url_for([user, :notification_preferences], only_path: true)
ArgumentError: wrong number of arguments (given 2, expected 0..1)
/actionpack-5.1.6/lib/action_dispatch/routing/url_for.rb:166:in `url_for'

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


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

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

低效不雅,因为它会生成一个包含额外不需要的信息的字符串,然后必须对其进行解析,将其转换为结构化数据结构,然后再将其转换返回到一个字符串没有不需要的信息。

不一致因为:

    Rails 为每个 _url 路由助手包含一个 _path 变体。为什么它没有url_for 的内置“路径”变体(或者有它并且它被称为其他东西?)?

    其他接受对象或数组的路由助手——例如polymorphic_path——也可以让你传递选项:

例子:

main > app.polymorphic_url [user, :notification_preferences], anchor: 'foo'
=> "http://example.com/users/4/notification_preferences#foo"

main > app.polymorphic_path [user, :notification_preferences], anchor: 'foo'
=> "/users/4/notification_preferences#foo"

main > app.polymorphic_path user, anchor: 'foo'
=> "/users/4#foo"

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

  # strategy for building urls to send to the client                        
  PATH    = ->(options)  ActionDispatch::Http::URL.path_for(options)      
  UNKNOWN = ->(options)  ActionDispatch::Http::URL.url_for(options)     

  def path_for(options, route_name = nil)                                   
    url_for(options, route_name, PATH)                                      
  end                                                                       

  # The +options+ argument must be a hash whose keys are *symbols*.         
  def url_for(options, route_name = nil, url_strategy = UNKNOWN)            
    options = default_url_options.merge options    
    ...

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

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


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

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

def url_for(object = nil, **options)
  puts "object: #object.inspect"
  puts "options: #options.inspect"
end

main > url_for ['user', :notification_preferences], anchor: 'anchor'
object: ["user", :notification_preferences]
options: :anchor=>"anchor"
=> nil

main > url_for ['user', :notification_preferences], only_path: true
object: ["user", :notification_preferences]
options: :only_path=>true

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

您将如何修改 url_for/full_url_for 以使其尽可能向后兼容,同时还允许您使用数组 + 关键字选项调用它?

【问题讨论】:

【参考方案1】:

这是一种潜在的解决方案,显示了我正在寻找的东西:

  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

(与original Rails version的来源比较)

我还没有尝试针对这段代码运行 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"

如果您发现其中的任何错误或有任何改进或可以想出更简洁的方法来完成此操作,请告诉我。

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

【讨论】:

以上是关于从数组或对象(而不是哈希)生成路由时,如何将“only_path:true”和“anchor:”等选项传递给“url_for”的主要内容,如果未能解决你的问题,请参考以下文章

react-router 如何从数组生成路由?

从 UIImage 生成哈希

在 C 中寻找数组(与链表)哈希表实现

如何推送对象值而不是数组中对象的引用

如何将二维数组直接添加到哈希图中? [复制]

有关java中的hashCode问题