I'm trying to change a function — which currently only accepts a URL (path) string — to make it more flexible, so that it will also accept as input the same arguments that you can pass to url_for
. The problem is, url_for
returns a full URL including protocol/host, and I only want the path portion...
url_for
has a lovely only_path: true
option that makes it skip adding the protocol/host. This works great as long as you're passing it as part of a single hash to url_for
:
main > app.url_for(controller: 'users', action: 'index', only_path: true)
=> "/users"
But how do you pass options when you are passing an array or a model object to 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'
You can't! Clearly, it's not even syntactically possible to pass an options hash if you're passing anything other than a hash, since its arity is 0..1
and it only takes a single argument: url_for(options = nil)
.
So my question is, is there a Rails helper that takes the same options as url_for
(including anchor:
) but returns a path?
I suppose it wouldn't be too hard to add one, like this one, that uses URI.parse(path).path
(probably what I'll do for now)... but that seems inefficient, inelegant, and inconsistent.
Inefficient and inelegant because it generates a string containing extra unwanted information and then has to parse it, convert it to a structured data structure, and then convert it back to a string without the unwanted information.
Inconsistent because:
Rails includes a
_path
variant for every_url
route helper. Why doesn't it have a built-in "path" variant ofurl_for
(or does it and it's called something else?)?Other routing helpers that accept an object or array—such as
polymorphic_path
— let you pass in options too:
Example:
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
actually has a 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
...
— just not ActionDispatch::Routing::UrlFor
, apparently. Anyway, ActionDispatch::Routing::RouteSet#path_for
is buried too deep in internals to be helpful to me; I need a helper that is callable from the controller/view.
So, what is a good solution for this that is elegant, consistent with other Rails routing helpers, and relatively efficient (no URI.parse
)?
Better yet, is there any reason the method signature of the built-in url_for
couldn't simply be modified (in a future version of Rails, by submitting a pull request) to allow both a subject (model object, array, or hash) and any number of optional options
to be passed in?
Probably the original url_for
was written before Ruby had keyword arguments. But nowadays, it's pretty trivial to do exactly that: accept an "object" plus any number of optional keyword options:
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}
Is there any reason we couldn't/shouldn't change url_for
's method signature to url_for(object = nil, **options)
?
How would you modify url_for
/full_url_for
such that it remained as backwards compatible as possible but also let you call it with an array + keyword options?