How to Override the Named Route Parameter ID in Rails

3 min read

In the previous posts, we've learned about the concepts of dynamic segments as well as named routes in Rails. Naming a route generates two helper methods which can be used to generate the route URL, whereas a dynamic segment allows us to create dynamic routes, where the parts of the URL can vary.

In this post, we'll combine these two concepts. Consider the following named route which contains a dynamic segment called :id:

# config/routes.rb
get "item/:id", to: "items#show", as: "item"

When the application receives a request on /item/4, it forwards it to the ItemsController#show action. You can access the ID of the post as follows: params[:id].

To generate the item URL, you can use the named route helper methods item_path and item_url, passing the ID of the post.

item_path(id: @item.id) # /item/10

In this example, @item is an instance of a Rails model (don't worry if you don't know what a model is, we'll learn it in detail later).

Typically, most of the time you'll work with the id of a model. To make this convenient, Rails makes the above named helper method call even shorter. Just pass the id, without the :id key.

item_path(@item.id)  # /item/10

You can go even one step further. Simply pass the instance, and Rails will figure out the id from the object.

item_path(@item)

It's recommended that you use the last syntax as not only it's shorter, but also it's very readable. Just reading item_path(@item) tells us that we need the path to an item.

At this point, you may be wondering: How does Rails know to call the id method on the passed instance, i.e. @item?

Answer: Behind the scenes, Rails calls the to_param method on the provided object. The to_param method is defined on all ActiveRecord models, and it returns the id of the model by default. That's how Rails knows to find the proper ID of the model when you pass it directly to the helper method, i.e. item_path(@item).

If there are multiple dynamic segments in the route, you can provide multiple corresponding models.

get "section/:section_id/article/:id", to: "articles#show", as: "section_article"

section = Section.first
article = session.articles.first
section_article_url(section, article) # /section/1/article/2

You must provide the model arguments to the helper method in the same order in which their IDs occur in the dynamic segments.

So here's what we've learned so far: Rails will infer the model ID passed to the named route helper method. It does so by calling the to_param method on the provided model instance, which returns the model's id by default.

That raises the question, can we customize the value of the dynamic segment to use another value, instead of using the default model id value? We can. Simply override the id value by defining a to_param method in your model and returning the value you want to insert.

For example, instead of an id, you want to use the slug of a post to identify it, i.e. instead of /posts/10 where 10 is the ID of the post, you want /posts/ruby-on-rails where ruby-on-rails is the slug of the post titled "Ruby on Rails" (a slug is typically the lowercased, dasherized version of the title).

To achieve this, you will override the to_param method in the Post model as follows:

class Post < ApplicationRecord
  def to_param
    title.parameterize
  end
end

After this, whenever you call the named helper methods post_path(post), or post_url(post) it will produce the URL containing the slug of the post.

@post = Post.create(title: "Ruby on Rails")

# in view
post_path(@post) # /posts/ruby-on-rails

Finally, you can update the dynamic segment from :id to :slug, so that you can use params[:slug] in the controller to extract the slug from the URL.

get "posts/:slug", to: "posts#show", as: "post"

That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.