We're hiring!

We're actively seeking developers & designers for our new Detroit location. Learn more

Subdomains in Rails

These days Atomic Object has me working on CircleBuilder, a social networking application for faith-based organizations. Each organization gets to set up its own social network. Think Ning for churches.

We do this by giving each organization their own subdomain off circlebuilder.com. The most important consideration is that data belonging to one organization must not be displayed in other organizations. We knew going in that this would be very easy to overlook while under pressure to get tasks done. We also knew that the customer would simply expect it to work, and would not mention the constraint when defining stories. To prevent embarrassing bugs, we nailed down a consistent approach that could guarantee we filtered data correctly.

It all revolves around a global before filter with this line:


  Organization.host = request.host

Our organization model looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  class Organization < ActiveRecord::Base
    def self.host=(value)
      Thread.current["organization_host"] = value
      Thread.current["organization_current"] = nil
    end
  
    def self.current
      host = Thread.current["organization_host"]
      raise "You must set the host before attempting to fetch the current organization." unless host
      Thread.current["organization_current"] ||= Organization.find_by_host(host)
    end
    
    has_many :members
  end

Now any code throughout the system can get the members of the current organization in a way that’s guaranteed to filter out other organizations:


  Organization.current.members

What if we need more complicated logic for filtering data? It’s common to put a class method on the model for which we’re searching, but instead we’ll put an instance method on the organization. This allows us to have an ironclad rule that all searches go through the organization. It also forces us to always remember to filter by organization.

1
2
3
4
5
6
  class Organization < ActiveRecord::Base
    ...
    def members_attending_sufficient_events(required_event_count)
      members.find(:all, :conditions => ["rsvp_count >= ?", required_event_count])
    end
  end

This can also be accomplished with named scopes or dedicated finder objects. Whichever way you choose, it’s important to have a very clear policy on how to find data that is bound to an organization. Consistency makes it much easier to prove that data isn’t bleeding across organization boundaries.

Setting up your server to respond to subdomains is usually as simple as giving it a wildcard domain like *.circlebuilder.com.

Running the app locally in development becomes slightly more awkward when you need subdomains. One solution is to edit your hosts file, pointing test.localhost to the localhost address, 127.0.0.1. For more information on the hosts file, including where to find it on your computer, check Wikipedia.

Ultimately, the devil’s in the details, but this should get you started on building an application that respects subdomains.

This entry was posted in Web. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

One Comment

  1. Posted October 7, 2009 at 4:19 pm

    I have been using a gem called “ghost” to handle multiple subdomains pointing to localhost. It provides a nice command line interface as opposed to hand editing a hosts file.

    It can be installed with the standard: sudo gem install ghost

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">