November 12, 2006
Older: Action Based Layouts
Newer: Class and Instance Variables In Ruby
Building A Multi-Site Application
Subdomains as account keys and multi-site applications are all the rage. All the cool kids are doing it and you want to as well but you just haven’t tried to figure it out yet. Well, if that is you then this is the article to read. Multi-site support is really easy with Rails and this article will go through the basics of getting a development environment setup on your mac (sorry windows users).
Creating the Rails App
Let’s create a new rails app ($ rails multisite
), change to the new directory ($ cd multisite
) and freeze it to edge rails ($ rake rails:freeze:edge
).
Next we need to create the databases for development and test:
$ mysqladmin -u root create multisite_development
$ mysqladmin -u root create multisite_test
Now we’ll install a handy plugin created by DHH for working with subdomains and such:
$ script/plugin install http://dev.rubyonrails.org/svn/rails/plugins/account_location/
In order to actually show how this works, we need to have something to work with in the database so we’ll generate a new resource (which will create some scaffolding, a model and a migration).
$ script/generate scaffold_resource site name:string subdomain:string created_at:datetime updated_at:datetime
The above piece of code generated a migration for us, so let’s migrate to the newest version.
$ rake db:migrate
You should see output similar to below:
== CreateSites: migrating =====================================================
-- create_table(:sites)
-> 0.1107s
== CreateSites: migrated (0.1115s) ============================================
Now it’s time to start up our app…
$ script/server
and open up your favorite browser (hopefully Firefox) to http://localhost:3000/sites/
so that we can enter a few test sites.
Click the ‘New site’ link and enter ‘Notre Dame’ for the name and ‘notredame’ for the subdomain. Press the ‘Create’ button, then click on the ‘back’ link, followed by the ‘New site’ link again. This time enter ‘Ohio State’ for the name and ‘ohiostate’ for the subdomain. Hit the ‘Create’ button again. You should now have a list view similar to the one below.
Setting Up Your Mac
Open up the application named NetInfo Manager (it is located in Applications/Utilities). Click the lock to make changes and enter your password.
Now click on machines and then on localhost.
With localhost still selected, click duplicate and confirm the dialog box.
You should now have a machine named localhost copy. Rename this ‘notredame.multisite.local’ by double clicking on the name value.
Now click back to localhost, click save and then click update copy (as the dialog’s appear). Repeat this step to create a machine named ‘ohiostate.multisite.local’. You now have two fake sites that you can tinker on your app with. To test it out, point your browser to http://notredame.multisite.local:3000/sites. You should get the same result as http://localhost:3000/sites.
But Which Site Am I On?
Now that we have the test sites created in the database and spoofed on our local machine, it’s time to add in the multi-site functionality. Because all controllers inherit from ApplicationController, all we need to do is add a before filter to it that figures out what site we are looking at and stores it. Open up app/controllers/application.rb
and add in the following lines of code.
class ApplicationController < ActionController::Base
include AccountLocation
before_filter :find_current_site
helper_method :current_site
attr_reader :current_site
private
def find_current_site
@current_site = Site.find_by_subdomain(account_subdomain)
end
end
So what does all that do? The first line includes the Account Location plugin methods in application controller. Next, I set a before filter to find the current site. After that, I added current_site
as a helper method so that it can be used in the view (if necessary). The last line before the private declaration creates a read method for the instance variable current_site so that we can access the current site in all of our controllers.
The final piece of finding the current site is the find_current_site
method. This attempts to find the current site based on the subdomain and set current_site
equal to it. account_subdomain
is a method from the account location plugin that we installed a bit ago.
Lastly, you could add the following line in your index.rhtml file to make sure that the current site is getting set properly.
<p><%= current_site.inspect %></p>
Now visit http://notredame.multisite.local:3000/sites/ and http://ohiostate.multisite.local:3000/sites/. Notice how the output of the inspection of current site differs. Below is a screenshot of when I visit the Notre Dame local site.
That is it. Not nearly as difficult as you thought, eh? In case you didn’t follow along, you can download an archive of the app I just created (Edge Rails at the time of this post is also included). Sometime this week I’m going to write part two to this that shows how to write functional tests for multi-site apps.
14 Comments
Nov 13, 2006
I’m bookmarking this for when I need it right now. Out of interest, is it possible to run multiple domains (as opposed to subdomains) off one rails app. Can I point www.account1.com and www.account2.com to the same app?
Nov 13, 2006
Daniel – if all you need is two domains pointed to the identical app, it’s easy – just set up both sites to point the same folder on your server. If you want to make the app behave differently based on the domain name, it’s basically the same thing – except you’ll need to apply similar techniques as this article (some routing inside your app).
John – it’s worth noting that deploying the app means you need to set up wildcard DNS so that *.yourapp.com points to the right place. Shared hosts don’t always let you do this, either – Dreamhost says they generally won’t do it. “DreamHost may, in special cases, set this up for a customer.” As an alternative, you could write a script to create the proper DNS entry when the new site is created. Either way, your app won’t do it all alone.
Nov 13, 2006
@Daniel – Yep, just change subdomain to domain and use find_by_domain(account_host) or something like that.
@Chas – Nope this won’t do it all. This article is for setting up your local computer, not a remote server.
Nov 13, 2006
Thanks for posting this, it was absolutely perfect. I had to restart my server since I absent-mindedly left it running.
Nov 14, 2006
@Garrett – No problem. Glad you found it helpful. There’s more to come. :)
Nov 14, 2006
I found your walkthrough helpful although you missed a step: after “Now click on machines and then on localhost.” and before “You should now have a machine named localhost copy.”, add that you should click on the “duplicate” button.
Nov 16, 2006
Just implemented your code and it works wonderfully. Thanks! Have a few extra tips to share.
For domains instead of subdomains use find_by_domain(account_domain). Just remember that is you are running your server on port 3000, your domain entry in your database will have to be domain.com:3000.
Also if you are on a Windows box you will have to edit your hosts file instead of the OSX NetInfo Manager. You can find it at c:\windows\system32\drivers\etc\hosts.
Nov 17, 2006
@Eric – I didn’t have to add the port to the database on the actual project I am working on, however, I may have modified the account location plugin. The project that I pulled this article from works by the full domain, I just figured people would be more interested in a subdomain article.
Feb 11, 2007
Now we want to buy machines for the industry of scaffolds: (PROPS, Pipe, Cup lock, Unvrsail-Jack ) Therefore, we hope that you help us to get the Pipes for the scaffolds industry: (PROPS, Cup lock, Unvrsail-Jack)
Mar 07, 2007
Great solution! One question, wouldn’t it be better to put the data that currently exists in your sites table in a hash so we don’t have to make a trip to the database on every request?
Mar 07, 2007
@Tony – I don’t think it would be, at least not in the app that I extracted this article from. If it was in a hash, each time I added a new site I would have to redeploy the entire application. With the sites stored in a database, I can modify sites and their configurations without redeploying and restarting mongrel. The database allows for more flexibility in this area.
Also, most apps will have more information pertaining to sites that just a name and domain. Managing that information in a hash would quickly become unmanageable.
Finally, by having sites stored in the database, I can use the rich domain modeling of rails.
Mar 14, 2007
Gotcha. To minimize the number of DB calls I’m putting the current site in the session.
def find_current_site
if !session[:currentsite].nil?
@current_site = session[:currentsite]
else
@current_site = Site.find_by_domain(account_domain)
session[:currentsite] = @current_site
end
end
I wish there was a way to pull the current site into memory when the web server starts so I don’t have to make but one trip to the DB but I guess this solution is ok.
Mar 23, 2007
I’m not a big fan of storing entire objects in the session.
Jun 11, 2007
Hi there,
I’m hosting an application (in production mode) with mongrel and apache (mongrel_cluster on ports 8000 to 8001) on a ubuntu feisty fawn server.
Can someone tell me how I can do the following (or tell me where I can a tutorial to do it):
- To have 2 differents domain names (dom1.com and dom2.org for an example) pointing the same rails app (but I would like that my rails app can know what domain was used : it’s for a multi-language site) What to do in apache ? in mongrel ? in rails ?
- To use my server to host more than 1 ruby app and for different users (rails_app1 in /home/user1/public_html/rails_app1 ; rails_app2 in /home/user2/public_html/rails_app2 ; … and so on)
For the moment I have mongrel (launched as user user1) and apache for hosting rails_app1 in /home/user1/public_html/rails_app1 (I made my set-up following http://mongrel.rubyforge.org/index.html and http://blog-perso.onzeweb.info/2006/12/16/rails-mongrel-apache-ubuntu-production/#comment-2135)
I’ve read this site who explained how to manage subdomain. I didn’t understood everything (I’m not english native) but I don’t think this is what I really need.
Thanks for help
Sorry, comments are closed for this article to ease the burden of pruning spam.