This article was published on March 1st 2019 and received its last update on August 1st 2020. It takes about 2 minutes to read.
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.
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.
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.
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).