Michael Trojanek (relativkreativ) — Bootstrapper and creator of things

This article was published on March 1st 2019 and received its last update on August 1st 2020. It takes about 2 minutes to read.

How to preserve the query string when redirecting routes in a Rails application

Redirecting a route in a Rails app while keeping the original request's query string intact is no default behaviour, but it can be done quite easily with a little thought.

Rails' provides the redirect method (defined in the ActionDispatch::Routing::Redirection module) which allows us to redirect any URL to another one from within our routes.rb file:

Unfortunately, this method discards certain parts of the original URL, as the docs clearly state:

get "/stories" => redirect("/posts")

[The redirect method] will redirect the user, while ignoring certain parts of the request, including query string, etc. /stories, /stories?foo=bar, etc. all redirect to /posts.

There are several use cases where this is undesired behaviour — we often need to keep the original request's query string.

The inline method

We can preserve the original request's query string by calling the redirect method with a block:

get '/stories', to: redirect { |params, request| URI.parse(request.original_url).query ? "/posts?#{URI.parse(request.original_url).query}" : "/posts" }

While this inline trickery certainly works, it becomes messy once we need to redirect more than one route.

A more DRY and elegant approach is writing our own redirector class.

The redirector class

Redirector classes are usually rather small, so defining them in our application's routes.rb file does not add a lot of clutter and makes sense:

class QueryRedirector
  def call(params, request)
    uri = URI.parse(request.original_url)
    if uri.query
      "#{@destination}?#{uri.query}"
    else
      @destination
    end
  end

  def initialize(destination)
    @destination = destination
  end
end

A redirector class needs to implement a call method which receives two arguments: The symbolized path parameters and the request object (as we already saw when we called the redirect method with a block).

The implementation is up to us. In this example, our redirector checks the original request for query parameters and if it finds any, it appends them to the new destination we need to provide to its initializer.

Use the class in our routes

In order to use it, we provide the redirect method with a new QueryRedirector instance:

get "/stories", to: redirect(QueryRedirector.new("/posts"))

This will redirect /stories?foo=bar to /posts?foo=bar, but /stories to /posts (no unnecessary ? because the original request does not have a query string).

Get in the loop

Join my email list to get new articles delivered straight to your inbox and discounts on my products.

No spam — guaranteed.

You can unsubscribe at any time.

Got it, thanks a lot!

Please check your emails for the confirmation request I just sent you. Once you clicked the link therein, you will no longer see these signup forms.