In Rails, the url_for
helper method is used to generate URLs with given set of parameters for different actions within our application.
# Generating a URL for a specific controller action
url_for(controller: 'posts', action: 'show', id: 1)
# Output: "/posts/1"
# Generating a URL with named routes
url_for(controller: 'posts', action: 'index')
# Output: "/posts"
# Generating a URL with additional parameters
url_for(controller: 'posts', action: 'index', page: 2)
# Output: "/posts?page=2"
# routes.rb
Rails.application.routes.draw do
get 'posts/:id', to: 'posts#show', as: 'post'
end
# Generating a URL using a named route
url_for(post_path(1))
# Output: "/posts/1"
# Generating a URL for a specific object
post = Post.find(1)
url_for(post)
# Output: "/posts/1"
Before
Before Rails 7.1, when routes were scoped (e.g., under user_id
), generating links for scoped routes in view files require explicit specification of user_id
in every instance, creating repetitive code for link generation.
Rails.application.routes.draw do
scope "user_id" do
get "/posts", to: "posts#index", as: :posts
get "/posts/:id", to: "posts#show", as: :post
end
get "/comments", to: "comments#index", as: :comments
delete "/signout", to: "sessions#destroy", as: :signout
end
<!-- app/views/posts/index.html.erb -->
<a href="<%= posts_path(user_id: current_user.id) %>"> Post </a>
Instead of passing user_id
to every scoped URL, we can set a default value for user_id
using default_url_options
method inside ApplicationController
.
default_url_options
method allows us to set default URL options that will be used by URL-generating methods, such as url_for
, across the entire application.
class ApplicationController < ActionController::Base
def default_url_options
{ user_id: current_user.id }
end
end
However, with the default_url_options
configuration, all routes, those lived outside the user_id
scope (like authentication routes or comment routes) will have ?user_id=current_user.id
query parameter at their end.
posts_path # => /current_user.id/posts
comments_path # => /comments?user_id=current_user.id
signout_path # => /signout?user_id=current_user.id
After
The above behavior causes URLs for non-scoped routes to include the scoped parameters set by default_url_options
, leading to potential confusion, incorrect URLs,
and aesthetics issues in scenarios where the parameters are not expected or needed.
The introduction of path_params option in Rails 7.1 for url_for
method addresses the above issue by allowing specified parameters to be used exclusively for named segments of the route, avoiding their addition to non-segment parts of URLs.
class ApplicationController < ActionController::Base
def default_url_options
{ path_params: { user_id: current_user.id } }
end
end
url_for
helpers will now behave as follows:
posts_path # => /current_user.id/posts
posts_path(user_id: 2) # => /2/posts
comments_path # => /comments
comments_path(user_id: 2) # => /comments?user_id=2
signout_path # => /signout
signout_path(user_id: 2) # => /signout?user_id=2
Lets discuss the above example in detailed manner:
-
posts_path
generates the path/current_user.id/posts
, using the defaultuser_id
provided bypath_params
. -
posts_path(user_id: 2)
generates the path/2/posts
, explicitly specifyinguser_id
as 2. It overrides the defaultuser_id
provided bypath_params
. -
comments_path
generates the path/comments
without any additional parameters. -
comments_path(user_id: 2)
generates the path/comments?user_id=2
, addinguser_id
as a query parameter sincecomments_path
doesn’t have a named segment foruser_id
. -
signout_path
generates the path/signout
without any additional parameters. -
signout_path(user_id: 2)
generates the path/signout?user_id=2
, addinguser_id
as a query parameter, similar tocomments_path
.
Summary
Scoping routes under any value (e.g. user_id
) leads to redundancy, requiring explicit value (user_id
) inclusion in URL helper methods.
As default_url_options
affecting non-scoped routes like authentication ones by adding the scoped value (user_id
) as query params resulting in unexpected outputs.
Where as path_params
enables specified parameters to be exclusively used for named segments in scoped routing, preventing unnecessary additions to every URL.