#123
Aug 18, 2008

Subdomains

Learn how to unleash the full potential of subdomains with the subdomain-fu Rails plugin in this episode.
Download (32.8 MB, 18:21)
alternative download for iPod & Apple TV (23.9 MB, 18:21)

Resources

Domain Name Solutions

As mentioned in the episode, you’ll need to add each subdomain to /etc/hosts. This can be a problem if subdomains can be generated dynamically. It would be nice if we could add a catch-all domain. Here are a couple solutions.

One is to make a PAC file and set it up as the proxy in your web browser preferences. It might look something like this.

# proxy.pac
function FindProxyForURL(url, host) {
  if (shExpMatch(url,"*.local/*")) {
    return "PROXY localhost";
  }
  return "DIRECT";
}

Another solution is to set up your own domain name server on your localhost and create an A entry to catch all subdomains. Sorry I can’t provide detailed instructions for this. Use Google. :)

Source Code from Episode

script/plugin install git://github.com/mbleigh/subdomain-fu.git
sudo apachectl graceful
dscacheutil -flushcache

# /etc/hosts
127.0.0.1 personal.blog.local company.blog.local

# apache config
ServerAlias *.blog.local
# routes.rb
map.blog_root '', :controller => 'blogs', :action => 'show', :conditions => { :subdomain => /.+/ }
map.root :blogs

# initializers/subdomain_config.rb
SubdomainFu.tld_sizes = {:development => 1,
                         :test => 0,
                         :production => 1}

# controllers/application.rb
def load_blog
  @current_blog = Blog.find_by_subdomain(current_subdomain)
  if @current_blog.nil?
    flash[:error] = "Blog invalid"
    redirect_to root_url
  end
end

# articles_controller.rb
before_filter :load_blog

def index
  @articles = @current_blog.articles.find(:all)
end

def show
  @article = @current_blog.articles.find(params[:id])
  @comment = Comment.new(:article => @article)
end

def new
  @article = @current_blog.articles.build
end
<!-- blogs/index.html.erb -->
<%= link_to h(blog.name), blog_root_url(:subdomain => blog.subdomain) %>

Full Source Code

RSS Feed for Episode Comments 64 comments

1. Vasil Aug 18, 2008 at 06:16

Cool, I’ve been waiting for this episode. Thanks.


2. Mike Rowlands Aug 18, 2008 at 06:50

Fantastic episode, thank you.

I have an enterprise type question: If we take 37signals' Basecamp as an example of what we want to achieve in terms of separating customers logins and data by subdomain, presumably each database table would need a subdomain ID reference or else the whole thing would be a mess.

Is it normal to have one massive database (with the help of clustering, load balancing etc) or 1 database per subdomain (which presumably would be routing hell).

I just wondered what conventional wisdom is and how well this model scales?


3. Ryan Bates Aug 18, 2008 at 07:15

@Mike, I believe Basecamp does not make separate tables or databases for each subdomain. It's just one database with multiple tables split by models just like Rails does.

Usually when you're working with subdomains like this, you'll have a model which maps directly to the subdomain which you load in a before filter like I do here. In Basecamp the model could be Company, not Blog.

You then always fetch other models through the associations as I show in the episode. This way they're always scoped properly and you can't access models outside of the company/blog.


4. kris Aug 18, 2008 at 11:08

Thanks for the awesome cast, I will be using this in an upcoming project :)


5. jeff Aug 18, 2008 at 14:37

Great work ryan. I have everything working really well. Except for one thing. The conditions regular expression does not seem to be working. It still goes to the wrong url when there is no subdomain. I thought maybe it wasn't working so I tried it out. I hard coded a subdomain :subdomain=>/test/ and it worked as expected. I'm just curious if I am missing something. again thanks for all of your awesome free screencasts


6. Dan Croak Aug 18, 2008 at 15:34

subdomain-fu is pretty cool. We used it in http://hoptoadapp.com

One problem we had with subdomain-fu was that it broke autotest for the app. So, I forked and ham-handedly removed the autotest directory - http://is.gd/1IIT

On the topic of subdomains, I wrote (but haven't used yet in an app) a copycat of the excellent validates_email_format_of plugin for subdomains that someone might find useful - http://is.gd/10hL


7. Nelson Aug 18, 2008 at 15:59

I can't see the videocast in Linux (mplayer, vlc or totem) ... In fact I can see the video, but it's very slow, like 1 frame per second....


8. Kurt Aug 18, 2008 at 17:37

I also have problems viewing the movies in Linux. I have tried Kaffiene, VLC, xine, and mplayer. The problems started around episode 100. One player shows the video but doesn't play the sound, one plays the sound but the video is extremely choppy, one crashes and one does nothing. Maybe you changed some settings that affect the mov file?

I used to watch your screencasts religiously, but now I study the notes deeply instead. So your work is still incredibly useful to me, and engages my brain even more. I just don't get the full effect most of the time...


9. Ryan Bates Aug 18, 2008 at 18:30

@jeff, what is the "wrong url"? It should be going to whatever you specify with "map.root" when not passing a subdomain.

@Nelson and Kurt, I'm hoping to release the episodes in a more Linux friendly format in the future. Thanks for your feedback.


10. Adam Hill Aug 18, 2008 at 20:51

Awesome - I've been looking forward to this one :) Very handy for lots of my future projects.


11. dreamhost promo code Aug 18, 2008 at 22:07

Amazing efforts guys!

Thanks


12. Marc Bowes Aug 19, 2008 at 16:55

I've had no problems with railscasts on Linux. VLC doesn't work for me (I get audio, but the image freezes on the intro), but MPLayer works perfectly. (dev-SVN-r26753-4.1.2)

Thanks for the cast, Ryan.


13. Michael Graff Aug 20, 2008 at 07:27

Please don't use wildcard CNAMEs. Use wildcard A records, if you set up your own DNS server.

*.example.com. A 127.0.0.1


14. Ryan Bates Aug 20, 2008 at 08:09

@Michael, thanks for catching that. Fixed the show notes.


15. kenny Lövrin Aug 20, 2008 at 11:06

I have the same issue as Jeff..(i think) No matter what I specify in the conditional regex, it matches and sends me to the show action instead of falling through to the map.root ..

example, both test1.project.local, test2.project.local and project.local takes me to the show action of the controller..


16. Ryan Bates Aug 20, 2008 at 12:35

@kenny, hmm, that's strange. Try downloading the project for this episode and see if you get the same behavior there.

http://github.com/ryanb/railscasts-episodes/tree/master/episode-123


17. Marc Bowes Aug 21, 2008 at 00:38

I actually have a question which I've been meaning to ask for a while. Why do you use build (as opposed to new or create). I know build just makes the object in memory.. but doesn't new do the same thing (until you save it)?


18. kenny Lövrin Aug 21, 2008 at 01:10

@Ryan:
I just tested your project from github, and it does work.. Very strange, I cannot see any differences compared to mine (the only difference is that I'm using my work computer now, and yesterday personal, comp. But both running leopard with passenger on original apache and so on). I will try again later today, doing once again from start and see what happens.


19. Bryan Buchs Aug 21, 2008 at 06:25

The latest commit of PassengerPane to GitHub includes an "Aliases" field that takes care of the ServerAlias addition to the vhost AND updates your hosts file.


20. Ryan Bates Aug 21, 2008 at 11:41

@Marc, interesting, I didn't realize "new" worked when called through associations. But looks like it does from my brief testing. Anyone know if there are differences or side effects?

Still, "build" seems to be the convention so I'll probably stick with it, and "new" is kind of reserved for Ruby which is why I'm surprised it works.


21. Matthijs Langenberg Aug 22, 2008 at 11:09

I've actually taken this a step further by also rendering a different set of templates per blog.
So instead of rendering from /app/views, the app renders /app/views/domain. So it's one rails app serving n-blogs on full domains.
Get in tough with me if you want to share your thoughts on this topic.


22. Sam Millar Aug 23, 2008 at 13:36

Great stuff as always thank you very much!


23. Sebastian Aug 24, 2008 at 11:46

@Michael Graff - I think that wildcard for subdomains is the best solution. For apps like basecamp, we have to authorize usera for some domain just at the begining when he is loggin and save subdomain it session. This better solution, dont you think?


24. Eric M Aug 26, 2008 at 18:09

I'm having the same problem as jeff and kenny above. Conditions on a route don't seem to be working. For instance, the following route:

map.account_root '',
 :controller => 'account_root',
 :action => 'index',
 :conditions => { :subdomain => /.+/ }

keeps on redirecting to itself. The account_root controller tries to redirect to the index page if the current account cannot be found. With the conditions not working, it falls into a redirect loop.

Any ideas?


25. Rick Aug 26, 2008 at 20:16

Ray, how would I accomplish the following:

I have a website 'xyz.com' that dynamically generates sites for social networks and assigns them each a unique subdomain, ie sn1.xyz.com, sn2.xyz.com etc.

The 'site' controller handles all 'xyz.com' requests without the subdomain whereas the 'network' controller handles all requests that have the subdomain present.

How can I conditionally route incoming request based on whether the subdomain is present or not?


26. 2 College Bums Aug 27, 2008 at 23:26

We have a question on combining subdomains (done with SubdomainFu) and selenium-on-rails. Whenever we access a subdomain we get a same origin policy permissions error. How do we test an application using selenium that uses subdomains? Thanks!


27. Kurt Aug 28, 2008 at 18:48

@Marc, I tried MPlayer from the command line and it works fine. I guess I need to figure out what's different between clicking on an icon vs. running the program from the command line. Thanks for the clue.


28. Bryce Thornton Aug 28, 2008 at 21:26

I was having issues when trying to use the route in the screencast:

:conditions => { :subdomain => /.+/ }

This was not working for me when trying to hit the site using www.domain.com. "www" is ignored by default in subdomain_fu, but this condition seemed to treat the "www" like it was a true subdomain. The route I'm now using has this condition:

:conditions => { :subdomain => true }

This seems to work as expected with "www".


29. Loïc Boutet Aug 29, 2008 at 11:07

Very interesting screencast.
However after viewing it I have more questions ;)
You set up the blog as the subdomain and then in your controller you call something like @blog.posts.find(:id) in order to have only the posts associated with the blog. This is fine when you have few models and not really deep. But how would you do this if you have say comments on your articles? Checking if one comment belong to the blog is a bit more complicated. And if you can comment on the other's people comment, then it become really messy.
How would you do such a thing? I can't find a satisfying solution.


30. Kod Aug 29, 2008 at 15:37

Very usefull cast. I will add subdomains in my new application.


31. keyJ Sep 02, 2008 at 06:34

@jeff, @kenny, @erik
I had similar problems that you describe in your posts.
I found that I makes a difference whether you install it as a plugin (from github) or a gem. With the gem I could not get the route to accept the :subdomain-condition correctly. But then I installed the plugin with
./script/plugin install...
and all of a sudden it worked fine.
I'm workin on a debian-linux-machine (4.0) with Rails 2.1.0 if that is any help. I have no idea how this works on other setups. ;)

PS: don't forget the initializer-script for local development mode to set the number of tlds right.

@ryan: very nice screencast - I love your work. Thank you.


32. kenny Lövrin Sep 03, 2008 at 09:51

@keyJ,

I have tried both the gem and the plugin install, as I suspected that the problem was as you describe.. Still can't get it to work though..


33. Michael Graff Sep 08, 2008 at 09:17

@Sebastian

I never said wildcards were not a good use here (although I personally hate them) but a wildcard A record is safer to use, and more efficient than, a wildcard CNAME. For one, it is only one DNS lookup, where the other can be up to two (if the target of the CNAME is not the same as the parent. e.g., *.example.com CNAME www.example.ORG)

Wildcards in DNS don't often do what people expect. They don't behave like 'regular' wildcards, but as a strange beast at times. As an author of a DNS server (BIND 9) we often get bug reports about them. Use them carefully, is all I say!

That said, this use of them is common, and pretty tame.


34. swaggs Sep 09, 2008 at 00:16

We have used this in our latest project - but was wondering what SNAFUs, if any throwing in SSL into the mix - anyone has encountered?

Do I need to purchase a wildcard cert?


35. Rahvin Sep 12, 2008 at 06:13

Anyone else experiencing problems when using this in combination with will_paginate? When I visit the site with subdomain.domain.local, all the url_for and link_to's are correctly pointing to subdomain.domain.local/path, except for the will_paginate links. will_paginate generates links to domain.local/path.


36. John Dewey Sep 12, 2008 at 23:20

You can give railsolver a try for wildcarding hosts.

This plugin will "hijack" Ruby host(s) resolver aka(resolv-replace.rb), allowing programmatic host file resolution (including wildcard host resolution). ™

http://github.com/retr0h/railsolver/tree/master


37. JontheWayne Sep 13, 2008 at 21:49

Like several others in these comments, I had major problems with the routing working correctly like it does for Ryan in this screencast. FYI, I'm using webrick, and I'm on windows xp.

So I took Ryan's advice and installed his app.

What I discovered is that Ryan's app's routing worked at first, then stopped working once I added "www." in the main domain in my local machine's hosts file. The routing works fine for the blog app when you just have <code>127.0.0.1 personal.blog.local company.blog.local blog.local</code>. What's interesting is that I'm using webrick and not apache like Ryan is using in the screencast. You'll notice he didn't have to specify the main domain at all. If I leave out the main domain, I can't access it at all, so I have to specify it on my setup.

Taking out the "www." didn't fix my local app though, which drove me nuts for a long time. Then I tried installing the plugin instead of the gem I was using, and sure enough the routing started working properly. I don't know what the difference is, but I'm glad for routing that works now.

Summary: use the plugin, don't specify "www." for the main domain in your hosts file.


38. StartBreakingFree.com Sep 15, 2008 at 22:36

I had problems with the www working too. The plugin is awesome, but would be a lot better if this area could be cleaned up.

Initially I had to do this to get it to work:

# routes.rb
map.blog_root '', :controller => 'blogs', :action => 'show', :conditions => { :subdomain => /[^www]/i }
map.root :blogs

which regex matches when the subdomain is not "www"

Later I changed it to this which worked too and seemed simpler:

# routes.rb
map.root :blogs, :conditions => { :subdomain => false }
map.blog_root '', :controller => 'blogs', :action => 'show'

Notice the root has to come before blog_root in that scenario.

-Brian
StartBreakingFree.com


39. Brandan Lennox Sep 17, 2008 at 10:11

Thanks so much for this, Ryan! It came in really handy in one of my projects, cleaned it up right nice.

@Brian (#40):

Your first example wasn't matching what you think it was. The /[^www]/ regex is equivalent to /[^w]/, and matches any string containing a character that isn't a "w". So "www" would fail, but so would "w" or "ww" or "wwwww". Obviously a little contrived, but still not really what was intended. This seems to work, although it looks awfully hacky:

/^(?!www).+|www.+$/

Your second solution is much better.


40. Bill Eisenhauer Sep 21, 2008 at 07:47

Does anyone have a good resource for how to do domain mapping? Basically, the app knows accounts by subdomain, but the web server up the stack does the url rewriting. Haven't found a definitive bullet-proof resource on this.

Thanks,
Bill


41. JoP Sep 21, 2008 at 15:23

@Loïc, good question. I would like to know what people think the best solution for this is too. Ryan answers Mike (comments 2 & 3) by suggesting 'always fetch other models through the associations'. Surely not always?! Is the answer to include the company/domain id in strategic places on the DB? ie Denormalise a bit.

@Ryan. Thanks!


42. soumya Sep 29, 2008 at 05:07

I want to create a sub domain functionality for my project same as http://hoptoadapp.com/account/new. For that i have read this article(http://railscasts.com/episodes/123-subdomains) and try to do that. I just download the full source code "episode123/blogs" and try to run it on my windows platform. But i get an error message, here it is: ActionController::InvalidAuthenticityToken in BlogsController#create

ActionController::InvalidAuthenticityToken

Is their any solution then please let me inform.

platform:
windows
rails version:2.1.0
add 127.0.0.1 personal.blog.localhost to /etc/hosts file

Thanks,
Soumya


43. Federico Sep 30, 2008 at 11:35

Hi,

I didn´t quite undestand if I need to modify the vhost.conf if i am using script/server.

Also i didnt find the file on my local machine, I tried with one subdomain and it worked, but when i write request.subdomains.inspect i get an empty array.

thanks.


44. Tim Oct 06, 2008 at 03:57

Thanks for the great rails cast.

I am trying to install the plugin and it keeps coming up with hanging during the installation. Here is the output:
removing: /Users/Tim/Code/Ruby/RainbowCube/vendor/plugins/subdomain-fu/.git
Initialized empty Git repository in /Users/Tim/Code/Ruby/RainbowCube/vendor/plugins/subdomain-fu/.git/
fatal: exec index-pack failed.
fatal: index-pack failed
~/Code/Ruby/RainbowCube$ remote: Counting objects: 26, done.
remote: Compressing objects: 100% (19/19), done.

Can anyone help me??

Thanks,
Tim


45. Ted Oct 21, 2008 at 13:31

Great tutorial, will be needing this soon. Thanks Ryan


46. Andrew Nov 14, 2008 at 08:14

Good cast! Thanks.


47. Billee D. Nov 21, 2008 at 16:38

Awesome. Thanks so much, Ryan. It seems as though whenever I need to do something which is new for me you have a screencast about it.

Keep up the great work. You rock! :-)


48. mattlatmatt Nov 24, 2008 at 15:09

I just want to confirm that I was also having problems where, after setting up subdomain_fu the app seemed to think that 'www' was an account name and so through an error. I followed the advice of @bryce (#30 above) and simply changed:

:conditions => { :subdomain => /.+/ }

to

:conditions => { :subdomain => true }

This seems to be working fine - 'www.myapp.com' now defaults to the same place as 'myapp.com'


49. MTH Jan 06, 2009 at 06:29

Thank's a lot Ryan for covering this.

I've been waiting for a time when I'll use subdomain. And this happened today.
Without this episode ...it's sad to imagine.

Thanks!


50. Andrew - Beach Jewelry Jan 12, 2009 at 04:22

thanx for a nice article on the isssue of subdomains..Everyone has a "www" subdomain (In "www.yourdomain.com", the "www" part is the subdomain). The www subdomain is created by, and serves files from, your /htdocs/www directory on the filesystem.

To add more subdomains, just create new subdirectories in the /htdocs directory.

For instance, if you had the domain name mychevycars.com and wanted a subdomain for Corvettes, then you would just create the directory /htdocs/corvettes and upload the files in there that you wanted to see at http://corvettes.mychevycars.com


51. Laurens Feb 11, 2009 at 10:53

How can I make rails generate ALL urls to point to a valid subdomain?

Example:

personal.blog.local has a page with a url:
<%= link_to "Show blog", blog_root %>

If I follow your howto, this will work, obviously, but now my question is: how can I generate urls to, let's say the "edit" action?

<%= link_to "Show blog", edit_blog_root %> doesn't work. Kind of logical, since we didn't define it anywhere, but I still want to know how this can be done dynamically.

So the thing I'm looking for, is some kind of directive in the routes config file, that generates all kinds of default paths I can use. Something like this:

map.resources :blogs, [insert specific information here so that rails generates edit/destroy/show/etc... URLs]

Is this possible anyway?


52. Rich Apodaca Mar 06, 2009 at 17:30

Ryan, excellent screencast - thanks!

Like other commenters, I ran into an infinite redirect when when using the gem version and a non-valid subdomain on ubuntu and 2.2.2 and script/server.

The fix was simple. Use :subdomain => false on the redirect:

redirect_to root_url(:subdomain => false)


53. ariera Aug 31, 2009 at 10:57

BTW as of today with rails 2.3.2 the session domain thing has apparently changed.

Now it should be ActionController::Base.session_options[:domain] = '.blog.local'

and not [:session_domain]

Thx for the cast!


54. Swards Sep 25, 2009 at 10:36

Matthew Hollingworth has a great gem that plays well with routes. It's worth looking into - it avoids before filters or passing around :subdomain in url parameters.

http://code.matthewhollingworth.net/articles/2009-06-02-adding-subdomains-to-rails-routing


55. Thomas Mango Oct 20, 2009 at 17:56

If you're running Rails 2.3.x and the latest version of subdomain-fu, make sure you set

SubdomainFu.override_only_path = true

in your initializer or else passing :subdomain into URLs won't work.


56. get skelaxin Jan 10, 2010 at 15:06

grghrth grgrgr


57. buy skelaxin overnight Jan 10, 2010 at 15:11

buy skelaxin overnight


58. wholesale nike shoes Jan 13, 2010 at 22:53

A very good article, I will always come in.


59. wholesale scarves Jan 13, 2010 at 22:53

Such a good article, caught my sympathy!
-


60. {{!COL9)) Jan 23, 2010 at 19:04

{{!COL4))


61. {{!COL9)) Jan 23, 2010 at 19:52

{{!COL4))


62. augmentin online Jan 23, 2010 at 20:42

love this info i will read this more often


63. javon Jan 25, 2010 at 22:29

 this info i will read this more often


64. divisas forex Feb 03, 2010 at 09:06

goood info thank´s

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source