Rails Router: Dynamic Segments and Query Strings

3 min read

Consider this route:

get "profile" => "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.

Dynamic Segments

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.

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 URL localhost:3000/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
  # ... other information such as controller and action name
}

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".

Optional Dynamic Segments

You can also make your dynamic segments optional by wrapping them 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.

Segment Constraints

Sometimes you may want to add some constraints on the dynamic segments in a route. For example, you may want to restrict the id to three digits. For this, you can pass the :constraint option as follows:

get 'images/:id', to: 'images#show', constraints: { id: /\d{3}/ }

This route would match paths such as /images/293, but not /images/xyz.

The above route can be succinctly expressed by passing the segment key itself.

get 'photos/:id', to: 'photos#show', id: /\d{3}/

Query Strings

A query string is indicated by the first ? in the URL. It's used to pass additional data to the server.

http://example.com/admin?true

Rails allows you to handle query strings in a similar way to dynamic segments: by making them accessible in the params hash.

Consider the following route:

get 'photos/:id', to: 'photos#show'

If the user agent sends an HTTP request to the photos/1?hue=10, the params hash will contain:

params = {
  id: 1,
  hue: 10,
  # ...
}

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.