4

On Rails 3, I'm trying to redirect from a URL without a trailing slash to the canonical URL that has a slash.

match "/test", :to => redirect("/test/")

However, the route above matches both /test and /test/ causing a redirect loop.

How do I make it match only the version without the slash?

Lance Roberts
  • 22,383
  • 32
  • 112
  • 130
Shai Coleman
  • 868
  • 15
  • 17

5 Answers5

6

You can force the redirect at the controller level.

# File: app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  protected

  def force_trailing_slash
    redirect_to request.original_url + '/' unless request.original_url.match(/\/$/)
  end
end

# File: app/controllers/test_controller.rb
class TestController < ApplicationController

  before_filter :force_trailing_slash, only: 'test'  # The magic

  # GET /test/
  def test
    # ...
  end
end
Blake Taylor
  • 9,217
  • 5
  • 38
  • 41
3

I wanted to do the same to have a cannonical url for a blog, this works

  match 'post/:year/:title', :to => redirect {|env, params| "/post/#{params[:year]}/#{params[:title]}/" }, :constraints => lambda {|r| !r.original_fullpath.end_with?('/')}
  match 'post/:year/:title(/*file_path)' => 'posts#show', :as => :post, :format => false

then I have another rule which deals with the relative paths inside the post. Order is important, so former goes first and generic one goes second.

carlosayam
  • 1,396
  • 1
  • 10
  • 15
2

There is an option in ActionDispatch called trailing_slash you can use to force the trailing slash at the end of the URL. I'm not sure if it can be used in the routing definition.

def tes_trailing_slsh
  add_host!
  options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
  assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
end

In your case, the best way is to use Rack or your web server to execute the redirect. In Apache, you can add a definition such as

RewriteEngine on
RewriteRule ^(.+[^/])$ $1/  [R=301,L]

To redirect all routes without a trailing slash to the corresponding one with trailing slash.

Or you can use rack-rewrite to perform the same task in your Rails app at Rack level.

Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
  • rack-rewrite is an interesting option. Though if at all possible I'd prefer a solution in Rails without using additional middleware and without doing it on the webserver side. – Shai Coleman Dec 21 '11 at 16:41
  • 1
    Actually, when you call `redirect("/test/")` you're using a Rack middleware. ;) – Simone Carletti Dec 21 '11 at 17:45
1

Bullet-proof solution:

  before_action :force_trailing_slash

  ...

  private

  def force_trailing_slash
    return if trailing_slash?

    url = url_for \
      request.path_parameters
        .merge(request.query_parameters)
        .merge(trailing_slash: true)

    redirect_to url, status: :moved_permanently
  end

  def trailing_slash?
    URI(request.original_url).path.ends_with? '/'
  end
Paweł Gościcki
  • 9,066
  • 5
  • 70
  • 81
0

Maybe it works with

match "/test$", :to => redirect("/test/")
BvuRVKyUVlViVIc7
  • 11,641
  • 9
  • 59
  • 111