36

I understand there are a lot of questions that answer this. I'm familiar with .htaccess and nginx.conf methods, but I do not have access to such traditional configuration methods on Heroku.

Simone Carletti gave this answer that leverages Rails 2.x Metals, but I'm using Rails 3 and this isn't compatible:

Redirect non-www requests to www URLs in Ruby on Rails

Please note:

I'm not looking for a simple before_filter in my ApplicationController. I'd like to accomplish a rewrite similar to Simone's. I believe this is job for the webserver or middleware like Rack at the very least, so I'd like to leave this bit out of the actual application code.

Goal

redirect                to                  status
----------------------------------------------------
www.foo.com             foo.com             301
www.foo.com/whatever    foo.com/whatever    301

Only hosts matching /^www\./ should be redirected. All other requests should be ignored.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
maček
  • 76,434
  • 37
  • 167
  • 198
  • I don't understand the need for the each method. My middlewares don't have it, and they work like a charm. – radiospiel Jun 01 '13 at 12:46
  • Ah, now I see: self is returned as the response body in the redirection case, and the body must respond_to? :each. This is probably not the canonical way. I suggest just returning an empty string or [] as the response body instead. – radiospiel Jun 01 '13 at 12:59
  • 2
    I know I'm late to the party, but for the users googling and using this later on: Beware that this will probably make your tests fail in weird ways as the DEFAULT_HOST for rspec tests is "www.example.com" and all requests will get redirected, even within your tests! – valscion Jan 30 '14 at 22:30

9 Answers9

51

In Ruby on Rails 4, removing www. from any URL whilst maintaining the pathname can be achieved simply by using:

# config/routes.rb

constraints subdomain: 'www' do
  get ':any', to: redirect(subdomain: nil, path: '/%{any}'), any: /.*/
end

In contrast, adding www. to the beginning of any URL that doesn't already have it can be achieved by:

# config/routes.rb

constraints subdomain: false do
  get ':any', to: redirect(subdomain: 'www', path: '/%{any}'), any: /.*/
end
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oliver Morgan
  • 578
  • 6
  • 7
  • Marking this answer as accepted for Rails 4 users. If you're using rails `<= 3`, please see some of the other answers in this thread. – maček Jan 31 '14 at 01:49
  • This is pretty awesome! – alexzg Feb 15 '15 at 21:52
  • 2
    I'm not sure if it's something that I'm doing but when using the `constraints subdomain: false`, it actually does two redirects for some reason and ends up in `www.www.localhost:3000`. In the browser I can see it redirected `localhost:3000` => `www.localhost:3000` => `www.www.localhost:3000` – Leo Correa Sep 10 '15 at 17:17
  • 2
    The reason it wasn't working is because the router checks for a TLD and domain name such that `localhost` and `www.localhost` do not have subdomains. – Leo Correa Sep 10 '15 at 19:47
  • I want to redirect ONLY if the subdomain is empty, if there is already a subdomain then don't redirect. possible? – Blankman Jul 20 '16 at 16:05
  • I'd love to know where that `any: /.*/` is documented, because that is gold. – inopinatus Sep 27 '17 at 03:07
  • For those looking to match more than just get routes: match ':any', to: ..., via: :all (http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions) – pixelearth Jun 12 '18 at 18:42
13

There's a better approach if you're using Rails 3. Just take advantage of the routing awesomeness.

Foo::Application.routes.draw do
  constraints(:host => /^example.com/) do
    root :to => redirect("http://www.example.com")
    match '/*path', :to => redirect {|params| "http://www.example.com/#{params[:path]}"}
  end
end
Sean Schofield
  • 597
  • 6
  • 9
  • I want to redirect **without** www when www is specified. Can you adapt your answer. I will mark it as accepted if it works. :) – maček Nov 01 '10 at 05:51
  • 1
    is it also possible you could make it domain agnostic? E.g., something like `"#{params[:protocol]}://#{params[:host]}/#{params[:path]}"` ? – maček Nov 01 '10 at 05:53
12

I really like using the Rails Router for such things. Previous answers were good, but I wanted something general purpose I can use for any url that starts with "www".

I think this is a good solution:

constraints(:host => /^www\./) do
  match "(*x)" => redirect { |params, request|
    URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
  }
end
Duke
  • 7,070
  • 3
  • 38
  • 28
7

Take a look at this middleware, it should do precisely what you want:

http://github.com/iSabanin/www_ditcher

Let me know if that worked for you.

Ilya Sabanin
  • 786
  • 5
  • 9
  • Ilya, I'm marking this as accepted but I came up with a slightly more elegant/robust solution. According to the [Rack Spec](http://rack.rubyforge.org/doc/SPEC.html), "The Body itself should not be an instance of String, as this will break in Ruby 1.9." So, I made an adjustment here. Also, I'm using Rack::Request object for handling the URL and making the code a little cleaner. – maček Oct 29 '10 at 08:59
7

A one-line version of Duke's solution. Just add to the top of routes.rb

match '(*any)' => redirect { |p, req| req.url.sub('www.', '') }, :constraints => { :host => /^www\./ }
Will Koehler
  • 1,746
  • 22
  • 16
  • Thanks! Could you also make another version to include redirect from onedomain.com to onedomain.net for example? If the both domains share the same name, could it be like req.url.sub('www.','').sub('net','com') ? – Daniel Dener Mar 05 '13 at 18:55
6

In Rails 3

#config/routes.rb
Example::Application.routes.draw do
  constraints(:host => "www.example.net") do
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap { |x| x.host = "example.net" }.to_s
    }
  end
  # .... 
  # .. more routes ..
  # ....
end
Vikrant Chaudhary
  • 11,089
  • 10
  • 53
  • 68
3

For Rails 4 the above solutions have to be appended with the Verb construction e.g. via: [:get, :post]. Duke's solution becomes:

  constraints(:host => /^www\./) do
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
    }, via: [:get, :post]
  end
Jessie Dedecker
  • 6,719
  • 1
  • 17
  • 12
3

If you want to redirect from the top-level domain (TLD) to the www subdomain, use this code:

constraints :subdomain => '' do
  match '(*any)' => redirect { |p, req| req.url.sub('//', '//www.') }
end

Note: This code the use of sub, not gsub, because sub replaces the first occurrence of the double-slashes where gsub would replace all double-slashes.

scarver2
  • 7,887
  • 2
  • 53
  • 61
2

Nothing wrong with the approaches above, but there are also a couple of gems that provide Rack middleware to do this.

I like the way that they keep this behaviour separate from the app itself, but it's not a particularly strong argument either way. I also use middleware to do this when working with Sinatra, so prefer to use a technique that I can use on apps built from Rails and/or Sinatra (I often run Nesta embedded in Rails).

Anyway, here they are:

The first is simpler (and the one I've been using) while the second offers a couple more features (that I'm yet to need, but appreciate).

Graham Ashton
  • 614
  • 5
  • 10