Precise URL Generation in Rails 7.1: Introducing path_params

Generating URLs within Rails applications can involve specifying parameters for various routes. Prior to Rails 7.1, dealing with scoped routes presented a minor challenge.

Imagine your routes are configured with a :client_id scope, like this:

1
2
3
4
5
# config/routes.rb
scope ":client_id" do
  get "/orders", to: "orders#index"
  get "/orders/:id", to: "orders#show"
end

Previously, you needed to explicitly include the client_id in every instance where you generated a link for these scoped routes, even within your views. This could be cumbersome and error-prone.

One solution involved overriding the default_url_options method in your ApplicationController:

1
2
3
4
5
6
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def default_url_options
    { client_id: "1234" }
  end
end

While this ensured client_id was included in all URLs, it had a downside. All routes, including those outside the client_id scope, received the ?client_id=1234 query parameter:

1
2
orders_path  # => /1234/orders
products_path # => /products?client_id=1234

This wasn’t ideal, as it added clutter and potentially unnecessary information to URLs.

Rails 7.1 introduces path_params to the rescue! This new option within the url_for helper offers granular control over URL generation. You can pass a hash of parameters, and only those that align with named segments in the route will be used, while others are discarded.

With path_params, the default_url_options implementation becomes cleaner:

1
2
3
4
5
class ApplicationController < ActionController::Base
  def default_url_options
    { path_params: { client_id: "1234" } }
  end
end

Now, the url_for helper behaves as follows:

1
2
3
4
orders_path              # => /1234/orders
orders_path(client_id: "5678") # => /5678/orders
products_path             # => /products
products_path(client_id: "5678") # => /products?client_id=5678

The view file can now simply reference the path without explicitly passing client_id:

1
2
# app/views/orders/index.html.erb
<a href="<%= orders_path %>">Orders</a>

This simplifies your code and avoids unnecessary query parameters.