Dynamic Segment Keys

You may have seen route paths such as posts/:id. If you're new to routing, you might be wondering why there are symbols inside the URL. These symbol-like terms are called segment keys, which make your URLs dynamic. In this lesson, we'll learn about the dynamic segment keys.

Instead of hard-coding a URL which matches exactly one URL, segment keys allow you to match more than one URL. You can access the values of those keys in the params hash, e.g. params[:id].

Consider this route:

get "profile", to: "pages#profile"

As you can guess, when the Rails application receives a request on the /profile page, it will direct that request to the profile method on the PagesController class.

At the same time, Rails provides the controller and action name in a hash called params in the controller.

# controllers/pages_controller.rb

class PagesController < ApplicationController
  def profile
    puts params # {"controller"=>"pages", "action"=>"profile"}
  end
end

The main use of the params hash is to access the user-submitted information, such as query string parameters in the URL or the form data when the user submits the form.

So far, we've seen static URLs, such as /posts, /about, and /profile. However, often your application URLs may contain dynamic content, such as /posts/20, /users/2, or /articles/learn-ruby, where the numbers 20, 2, and the text learn-ruby can vary depending on what resource the user is trying to access.

If your routes have this kind of dynamic content, i.e. parts of the URL that can vary, the Rails routing system lets you define them in the route, and make them available in the params hash in the controller.

A segment key is just a variable prefixed with a colon, just like a symbol, inside a URL pattern. It lets you capture the value inside the URL.

You can make a route dynamic by providing segment keys in the URL pattern. Consider the following route with two dynamic segments, :section_name and :slug.

get 'section/:section_name/article/:slug', to: 'articles#show'

Now consider what will happen when you visit the following URL:

writesoftwarewell.com/section/rails/article/activerecord.

Rails will extract the values of dynamic segments :section_name and :slug, that is, rails and activerecord respectively; and make them available in the params hash:

# controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def show
    puts params
  end
end

# Output

params = {
  section_name: "rails",
  slug: "activerecord
  # ...
}

Now in your controllers, you can read these dynamic segments to generate and dispatch appropriate response, i.e. an article on ActiveRecord under the Rails section.

To generate the URL, you have to provide the values for the segment keys. For example, given this route:

get "songs/:genre", to: "songs#index", as: "songs"

Calling songs_path(genre: "pop") in your application will generate the path "/songs/pop".

Here's a segment key named :slug which lets you refer posts by their logical, dasherized name, i.e. ruby-on-rails.

get '/posts/:slug', to: 'posts#show'

No, not this slug!

a slug, or a snail

In the context of web applications, a Slug is the unique identifying part of a web address.

When you make a GET request to the URL matching the above route, e.g. /posts/understanding-router, Rails sets params[:slug] to understanding-router. You can access the slug value inside the controller.

class PostsController < ApplicationController
  def show
    @post = Post.find_by(slug: params[:slug])
  end
end

Note: If you use segment keys in a route, you have to provide their values when generating the URLs with the Rails helpers.

<%= link_to 'Understanding Rails', controller: 'posts', action: 'show', slug: 'understanding-rails' %>

Optional Segments

You can make your dynamic segments optional by wrapping the segment key in parenthesis. For example,

get 'songs(/:genre)', to: 'songs#index' 

This route will match both /songs/happy and /songs and will be routed to the index action on the SongsController class.

If you want to provide default values for the dynamic segments, you can do so by providing the :defaults option.

get "songs(/:genre)", to: "songs#index", defaults: { genre: "rock" }

When the user visits songs/pop, the params[:genre] will be pop. However, if they visit only /songs, the params[:genre] will be rock.

Pretty cool, right? Don't you just love Rails?


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. You can also subscribe to my blog to receive future posts via email.