Robby on Rails: Subdomain accounts with Ruby on Rails explainedthoughts.sort_by{|t| t[:topic]}.collect tag:www.robbyonrails.com,2005:TypoTypo2009-01-12T00:22:14-05:00Robby Russellurn:uuid:17655113-8e27-436e-b246-1677382e07462009-01-11T17:30:00-05:002009-01-12T00:22:14-05:00Subdomain accounts with Ruby on Rails explained<p><span class="caps">DHH</span> recently posted, <a href="http://www.37signals.com/svn/posts/1512-how-to-do-basecamp-style-subdomains-in-rails">How to do Basecamp-style subdomains in Rails</a> on SvN and it just happens that I was implementing some similar stuff this last week for a project we’re developing internally.</p>
<p>In our project, not everything needs to be scoped per-account as we are building a namespace for administrators of the application and also want a promotional site for the product. Three different interfaces, with some overlap between them all.</p>
<p>Let’s walk through a few quick steps that you can follow to setup the two interfaces within the same application.</p>
<p>Suppose that we’re going to build a new web-based product and have the following requirements initially.</p>
<ul>
<li>We need a promotional site for sign-ups, frequently-asked-questions, support requests, etc.</li>
<li>When people sign-up for an account, they’ll should have their own unique sub-domain</li>
<li>There are two different visual layouts (promotional site and the account)</li>
</ul>
<p>Note: I use RSpec and am going to skip the <span class="caps">TDD</span> process here and let you conquer that for yourself. Am using the default Rails commands in this tutorial.</p>
<h2>Account model / Database</h2>
<p>We’re going to generate a new model for Account, which will be responsible for scoping sub-domains and individual accounts.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Account</span> <span class="ident">subdomain</span><span class="symbol">:string</span>
<span class="ident">create</span> <span class="ident">app</span><span class="punct">/</span><span class="ident">models</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">unit</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">fixtures</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">app</span><span class="punct">/</span><span class="ident">models</span><span class="punct">/</span><span class="ident">account</span><span class="punct">.</span><span class="ident">rb</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">unit</span><span class="punct">/</span><span class="ident">account_test</span><span class="punct">.</span><span class="ident">rb</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">fixtures</span><span class="punct">/</span><span class="ident">accounts</span><span class="punct">.</span><span class="ident">yml</span>
<span class="ident">exists</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span>
<span class="ident">create</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span><span class="punct">/</span><span class="number">20090111220627_</span><span class="ident">create_accounts</span><span class="punct">.</span><span class="ident">rb</span></code></pre></div>
<p>Great, let’s migrate our application.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">rake</span> <span class="ident">db</span><span class="symbol">:migrate</span>
<span class="punct">==</span> <span class="constant">CreateAccounts</span><span class="punct">:</span> <span class="ident">migrating</span> <span class="punct">=================================================</span>
<span class="punct">--</span> <span class="ident">create_table</span><span class="punct">(</span><span class="symbol">:accounts</span><span class="punct">)</span>
<span class="punct">-></span> <span class="number">0.0045</span><span class="ident">s</span>
<span class="punct">==</span> <span class="constant">CreateAccounts</span><span class="punct">:</span> <span class="ident">migrated</span> <span class="punct">(</span><span class="number">0.0052</span><span class="ident">s</span><span class="punct">)</span> <span class="punct">========================================</span></code></pre></div>
<p>Before we get too far, let’s make sure that we’re adding an index on this table for the subdomain, as it’ll improve performance in the database as the subdomain will used in <span class="caps">SQL</span> conditions quite often.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">migration</span> <span class="constant">AddIndexToAccountSubdomain</span>
<span class="ident">exists</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span>
<span class="ident">create</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span><span class="punct">/</span><span class="number">20090111221009_</span><span class="ident">add_index_to_account_subdomain</span><span class="punct">.</span><span class="ident">rb</span></code></pre></div>
<p>Let’s open up this new migration file and toss in a <span class="caps">UNIQUE INDEX</span> on <code>subdomain</code>.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">AddIndexToAccountSubdomain</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Migration</span>
<span class="keyword">def </span><span class="method">self.up</span>
<span class="ident">add_index</span> <span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:unique</span> <span class="punct">=></span> <span class="constant">true</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">self.down</span>
<span class="ident">remove_index</span> <span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<p>Okay, let’s migrate this bad boy.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">rake</span> <span class="ident">db</span><span class="symbol">:migrate</span>
<span class="punct">==</span> <span class="constant">AddIndexToAccountSubdomain</span><span class="punct">:</span> <span class="ident">migrating</span> <span class="punct">=====================================</span>
<span class="punct">--</span> <span class="ident">add_index</span><span class="punct">(</span><span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="punct">{</span><span class="symbol">:unique=</span><span class="punct">></span><span class="constant">true</span><span class="punct">})</span>
<span class="punct">-></span> <span class="number">0.0047</span><span class="ident">s</span>
<span class="punct">==</span> <span class="constant">AddIndexToAccountSubdomain</span><span class="punct">:</span> <span class="ident">migrated</span> <span class="punct">(</span><span class="number">0.0050</span><span class="ident">s</span><span class="punct">)</span> <span class="punct">============================</span> </code></pre></div>
<p>Great, we’re now ready to move on to the fun stuff.</p>
<p>Let’s open up <code>app/models/account.rb</code> and throw some sugar in it.</p>
<h3>Data Validation</h3>
<p>Because we’re going to be dealing with subdomains, we need to make sure that we’re only allowing people to sign-up with valid data otherwise, there could be issues. URLs need to fit within certain conventions and we need to make it as graceful as possible for our customers.</p>
<p>Let’s make a quick list of what we need to enforce for the <code>subdomain</code> attributes. This can easily be expanded, but let’s cover the basics.</p>
<ul>
<li>Each account should have a <code>subdomain</code> </li>
<li>Each <code>subdomain</code> should be unique within the application</li>
<li>A <code>subdomain</code> should be alpha-numeric with no characters or spaces with the exception of a dash (my requirement)</li>
<li>A <code>subdomain</code> should be stored as lowercase</li>
</ul>
<p>So, let’s update the following default Account model….</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span> </code></pre></div>
<p>..and add some basic validations.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">validates_presence_of</span> <span class="symbol">:subdomain</span>
<span class="ident">validates_format_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:with</span> <span class="punct">=></span> <span class="punct">/</span><span class="regex">^[A-Za-z0-9-]+$</span><span class="punct">/,</span> <span class="symbol">:message</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">The subdomain can only contain alphanumeric characters and dashes.</span><span class="punct">',</span> <span class="symbol">:allow_blank</span> <span class="punct">=></span> <span class="constant">true</span>
<span class="ident">validates_uniqueness_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:case_sensitive</span> <span class="punct">=></span> <span class="constant">false</span>
<span class="ident">before_validation</span> <span class="symbol">:downcase_subdomain</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">downcase_subdomain</span>
<span class="constant">self</span><span class="punct">.</span><span class="ident">subdomain</span><span class="punct">.</span><span class="ident">downcase!</span> <span class="keyword">if</span> <span class="ident">attribute_present?</span><span class="punct">("</span><span class="string">subdomain</span><span class="punct">")</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<h4>Reserved subdomains</h4>
<p>In the project that our team is working on, we wanted to reserve several subdomains so that we could use them later on. We tossed in the following validation as well.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">validates_exclusion_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:in</span> <span class="punct">=></span> <span class="punct">%w(</span><span class="string"> support blog www billing help api </span><span class="punct">),</span> <span class="symbol">:message</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">The subdomain <strong>{{value}}</strong> is reserved and unavailable.</span><span class="punct">"</span></code></pre></div>
<p>This will prevent people from using those when they sign up.</p>
<h3>Controller / Handling Requests</h3>
<p>Let’s now think about how we’ll handle requests so that we can scope the application to the current account when a subdomain is being referenced in the <span class="caps">URL</span>.</p>
<p>For example, let’s say that our application is going to be: <code>http://purplecowapp.com/</code> [1]</p>
<p>Customers will get to sign-up and reserve <code>http://customer-name.purplecowapp.com/</code>. I want my account subdomain to be <code>green.purplecowapp.com</code> and everything under this subdomain should be related to my instance of the application.</p>
<p>I’ve begun working on my own module, which is inspired mostly by <a href="http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb">the account_location plugin</a> with some additions to meet some of our product’s requirements.</p>
<p>Here is my attempt to simplify it for you (removed some other project-specific references) and <a href="http://gist.github.com/45826">have put this into a Gist for you</a>.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="comment">#</span>
<span class="comment"># Inspired by</span>
<span class="comment"># http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb</span>
<span class="comment">#</span>
<span class="keyword">module </span><span class="module">SubdomainAccounts</span>
<span class="keyword">def </span><span class="method">self.included</span><span class="punct">(</span> <span class="ident">controller</span> <span class="punct">)</span>
<span class="ident">controller</span><span class="punct">.</span><span class="ident">helper_method</span><span class="punct">(</span><span class="symbol">:account_domain</span><span class="punct">,</span> <span class="symbol">:account_subdomain</span><span class="punct">,</span> <span class="symbol">:account_url</span><span class="punct">,</span> <span class="symbol">:current_account</span><span class="punct">,</span> <span class="symbol">:default_account_subdomain</span><span class="punct">,</span> <span class="symbol">:default_account_url</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="ident">protected</span>
<span class="comment"># TODO: need to handle www as well</span>
<span class="keyword">def </span><span class="method">default_account_subdomain</span>
<span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_url</span><span class="punct">(</span> <span class="ident">account_subdomain</span> <span class="punct">=</span> <span class="ident">default_account_subdomain</span><span class="punct">,</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="ident">http_protocol</span><span class="punct">(</span><span class="ident">use_ssl</span><span class="punct">)</span> <span class="punct">+</span> <span class="ident">account_host</span><span class="punct">(</span><span class="ident">account_subdomain</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_host</span><span class="punct">(</span> <span class="ident">subdomain</span> <span class="punct">)</span>
<span class="ident">account_host</span> <span class="punct">=</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="ident">account_host</span> <span class="punct"><<</span> <span class="ident">subdomain</span> <span class="punct">+</span> <span class="punct">'</span><span class="string">.</span><span class="punct">'</span>
<span class="ident">account_host</span> <span class="punct"><<</span> <span class="ident">account_domain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_domain</span>
<span class="ident">account_domain</span> <span class="punct">=</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="ident">account_domain</span> <span class="punct"><<</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">domain</span> <span class="punct">+</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">port_string</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_subdomain</span>
<span class="ident">request</span><span class="punct">.</span><span class="ident">subdomains</span><span class="punct">.</span><span class="ident">first</span> <span class="punct">||</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">default_account_url</span><span class="punct">(</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="ident">http_protocol</span><span class="punct">(</span><span class="ident">use_ssl</span><span class="punct">)</span> <span class="punct">+</span> <span class="ident">account_domain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_account</span>
<span class="constant">Account</span><span class="punct">.</span><span class="ident">find_by_subdomain</span><span class="punct">(</span><span class="ident">account_subdomain</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">http_protocol</span><span class="punct">(</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="punct">(</span><span class="ident">use_ssl</span> <span class="punct">?</span> <span class="punct">"</span><span class="string">https://</span><span class="punct">"</span> <span class="punct">:</span> <span class="punct">"</span><span class="string">http://</span><span class="punct">")</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<p><a href="http://gist.github.com/45826">View gist here</a> (embed wasn’t working right when I tried)</p>
<p>Just include this into your <code>lib/</code> directory and <code>require</code> it in <code>config/environment.rb</code>. (if people think it’s worth moving into a plugin, I could do that)</p>
<h4>Including AccountSubdomains</h4>
<p>In the main application controller (<code>app/controllers/application.rb</code>), just include this submodule.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">include</span> <span class="constant">SubdomainAccounts</span>
<span class="punct">...</span>
<span class="keyword">end</span> </code></pre></div>
<p>Now, we’ll want to add a check to verify that the requested subdomain is a valid account. (our code also checks for status on paid memberships, etc… but I’ll just show a basic version without that)</p>
<p>Let’s add in the following to <code>app/controllers/application.rb</code>. This will only check on the status of the account (via subdomain) if the current subdomain is not the default. For example: <code>purplecowapp.com</code> is just our promotion site, so we won’t look up the account status and/or worry about the subdomain. Otherwise, we’ll check on the status.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">before_filter</span> <span class="symbol">:check_account_status</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">check_account_status</span>
<span class="keyword">unless</span> <span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="comment"># TODO: this is where we could check to see if the account is active as well (paid, etc...)</span>
<span class="ident">redirect_to</span> <span class="ident">default_account_url</span> <span class="keyword">if</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">nil?</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<h4>Current Account meets Project model</h4>
<p>When requests are made to an account’s subdomain, we want to be able to scope our controller actions.</p>
<p><strong><span class="caps">WARNING</span></strong>: I’m going to gloss over the following steps because this is just standard Rails development stuff and I want to focus on how to scope your Rails code to account subdomains.</p>
<p>I’ll just say that this product gives each account many projects to do stuff within. I’ll assume that you’ll know how to handle all that and we’ll assume you have a Project model already.</p>
<p>What you will need is to add a foreign key to your table (projects in this example) that references Account. So, make sure that your model has an <code>account_id</code> attribute with and that the database table column has an <span class="caps">INDEX</span>.</p>
<p>We’ll add our associations in the models so that they can reference each other.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="comment"># app/models/account.rb</span>
<span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">has_many</span> <span class="symbol">:projects</span>
<span class="comment"># ...</span>
<span class="keyword">end</span>
<span class="comment"># app/models/project.rb</span>
<span class="keyword">class </span><span class="class">Project</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">belongs_to</span> <span class="symbol">:account</span>
<span class="comment"># ...</span>
<span class="keyword">end</span> </code></pre></div>
<p>Okay great… back to our controllers. The SubdomainAccounts module provides you with the <code>current_account</code> variable, which you can use within your controllers/views. This allows us to do the following in our controllers. For example, if we had a ProjectsController.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">ProjectsController</span> <span class="punct"><</span> <span class="constant">ApplicationController</span>
<span class="keyword">def </span><span class="method">index</span>
<span class="attribute">@projects</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:all</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">new</span>
<span class="attribute">@project</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">new</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">show</span>
<span class="attribute">@project</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="ident">params</span><span class="punct">[</span><span class="symbol">:id</span><span class="punct">])</span>
<span class="keyword">end</span>
<span class="comment"># ...</span>
<span class="keyword">end</span></code></pre></div>
<p>See, this wasn’t so hard, was it?</p>
<h2>Handling layouts</h2>
<p>I wanted to highlight one other thing here because I suspect that most projects that fit this will likely need a promotional/resource site where people will sign-up from. In our application, we have two application layouts. One for the main application that customers will interact with via their subdomain and the promotional site layout.</p>
<p>The default layout is just <code>app/views/layouts/application.html.erb</code> and we have our promotional site layout at <code>app/views/layouts/promo_site.html.erb</code>. A few of our controllers are specifically for the promotional site while the rest are for the application itself and in some cases, there is some overlap down to individual action within a controller.</p>
<p>What we did was add a few more before filters to our application controller to a) define the proper layout to render, and b) skip login_required on the promo site.</p>
<p>To have the proper layout get rendered, we’re just checking whether the current request was made to the promotional site or not.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ...</span>
<span class="ident">layout</span> <span class="symbol">:current_layout_name</span> <span class="comment"># sets the proper layout, for promo_site or application</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">promo_site?</span>
<span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_layout_name</span>
<span class="ident">promo_site?</span> <span class="punct">?</span> <span class="punct">'</span><span class="string">promo_site</span><span class="punct">'</span> <span class="punct">:</span> <span class="punct">'</span><span class="string">application</span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="comment"># ...</span>
<span class="keyword">end</span></code></pre></div>
<p>Our application is using Restful Authentication and we just want to check to see if the current request is made to the promotional site or not. If it is, we’ll skip the <code>login_required</code> filter. Let’s assume that you have the following before_filter set.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ...</span>
<span class="ident">before_filter</span> <span class="symbol">:login_required</span></code></pre></div>
<p>We’ll just change this to:</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ..</span>
<span class="ident">before_filter</span> <span class="symbol">:check_if_login_is_required</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">promo_site?</span>
<span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_layout_name</span>
<span class="ident">promo_site?</span> <span class="punct">?</span> <span class="punct">'</span><span class="string">promo_site</span><span class="punct">'</span> <span class="punct">:</span> <span class="punct">'</span><span class="string">application</span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">check_if_login_is_required</span>
<span class="ident">login_required</span> <span class="keyword">unless</span> <span class="ident">promo_site?</span>
<span class="keyword">end</span>
<span class="comment"># ...</span></code></pre></div>
<p>There we go. We can now render the proper layout given the request and only handle authentication when necessary.</p>
<h2>Development with account subdomains</h2>
<p>When you begin developing an application like this, you need to move beyond using <code>http://locahost:3000</code> as we need to be able to develop and test with subdomains. You can open up your <code>/etc/hosts</code> (within a Unix-based O/S) file and add the following.</p>
<pre><code>
127.0.0.1 purplecowapp.dev
127.0.0.1 green.purplecowapp.dev
127.0.0.1 sample.purplecowapp.dev
127.0.0.1 planetargon.purplecowapp.dev
127.0.0.1 lollipops.purplecowapp.dev
127.0.0.1 help.purplecowapp.dev
127.0.0.1 support.purplecowapp.dev
</code></pre>
<p>After you edit that file (with root permissions), you can flush your dns cache with <code>dscacheutil -flushcache</code> (Mac <span class="caps">OS X</span>). This will let you make requests to <code>http://purplecowapp.dev:3000/</code> and <code>http://green.purplecowapp.dev:3000</code>. This is a convention that our team has begun using for our own projects (TLD ending in <code>.dev</code>). It’s important to remember that the subdomain must be specified here in order to work for local requests. Unfortunately, hosts files don’t support wildcards (’*’).</p>
<h3>Update</h3>
<p>You can also use Ghost, which is a gem for managing <span class="caps">DNS</span> entries locally with Mac <span class="caps">OS X</span>. Read <a href="http://www.robbyonrails.com/articles/2009/01/12/get-to-know-a-gem-ghost">Get to know a gem: Ghost</a></p>
<h2>Summary</h2>
<p>I know that I glossed over some sections, but was hoping that the code itself would be the most beneficial for you. Feel free to leave any questions and/or provide some feedback on our approach. Perhaps you have some suggestions that I could incorporate into this so that we can improve on this pattern.</p>
<p id="fn1"><sup>1</sup> yeah, I’ve been reading more Seth Godin recently…</p><p><span class="caps">DHH</span> recently posted, <a href="http://www.37signals.com/svn/posts/1512-how-to-do-basecamp-style-subdomains-in-rails">How to do Basecamp-style subdomains in Rails</a> on SvN and it just happens that I was implementing some similar stuff this last week for a project we’re developing internally.</p>
<p>In our project, not everything needs to be scoped per-account as we are building a namespace for administrators of the application and also want a promotional site for the product. Three different interfaces, with some overlap between them all.</p>
<p>Let’s walk through a few quick steps that you can follow to setup the two interfaces within the same application.</p>
<p>Suppose that we’re going to build a new web-based product and have the following requirements initially.</p>
<ul>
<li>We need a promotional site for sign-ups, frequently-asked-questions, support requests, etc.</li>
<li>When people sign-up for an account, they’ll should have their own unique sub-domain</li>
<li>There are two different visual layouts (promotional site and the account)</li>
</ul>
<p>Note: I use RSpec and am going to skip the <span class="caps">TDD</span> process here and let you conquer that for yourself. Am using the default Rails commands in this tutorial.</p>
<h2>Account model / Database</h2>
<p>We’re going to generate a new model for Account, which will be responsible for scoping sub-domains and individual accounts.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">model</span> <span class="constant">Account</span> <span class="ident">subdomain</span><span class="symbol">:string</span>
<span class="ident">create</span> <span class="ident">app</span><span class="punct">/</span><span class="ident">models</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">unit</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">fixtures</span><span class="punct">/</span>
<span class="ident">create</span> <span class="ident">app</span><span class="punct">/</span><span class="ident">models</span><span class="punct">/</span><span class="ident">account</span><span class="punct">.</span><span class="ident">rb</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">unit</span><span class="punct">/</span><span class="ident">account_test</span><span class="punct">.</span><span class="ident">rb</span>
<span class="ident">create</span> <span class="ident">test</span><span class="punct">/</span><span class="ident">fixtures</span><span class="punct">/</span><span class="ident">accounts</span><span class="punct">.</span><span class="ident">yml</span>
<span class="ident">exists</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span>
<span class="ident">create</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span><span class="punct">/</span><span class="number">20090111220627_</span><span class="ident">create_accounts</span><span class="punct">.</span><span class="ident">rb</span></code></pre></div>
<p>Great, let’s migrate our application.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">rake</span> <span class="ident">db</span><span class="symbol">:migrate</span>
<span class="punct">==</span> <span class="constant">CreateAccounts</span><span class="punct">:</span> <span class="ident">migrating</span> <span class="punct">=================================================</span>
<span class="punct">--</span> <span class="ident">create_table</span><span class="punct">(</span><span class="symbol">:accounts</span><span class="punct">)</span>
<span class="punct">-></span> <span class="number">0.0045</span><span class="ident">s</span>
<span class="punct">==</span> <span class="constant">CreateAccounts</span><span class="punct">:</span> <span class="ident">migrated</span> <span class="punct">(</span><span class="number">0.0052</span><span class="ident">s</span><span class="punct">)</span> <span class="punct">========================================</span></code></pre></div>
<p>Before we get too far, let’s make sure that we’re adding an index on this table for the subdomain, as it’ll improve performance in the database as the subdomain will used in <span class="caps">SQL</span> conditions quite often.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">ruby</span> <span class="ident">script</span><span class="punct">/</span><span class="ident">generate</span> <span class="ident">migration</span> <span class="constant">AddIndexToAccountSubdomain</span>
<span class="ident">exists</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span>
<span class="ident">create</span> <span class="ident">db</span><span class="punct">/</span><span class="ident">migrate</span><span class="punct">/</span><span class="number">20090111221009_</span><span class="ident">add_index_to_account_subdomain</span><span class="punct">.</span><span class="ident">rb</span></code></pre></div>
<p>Let’s open up this new migration file and toss in a <span class="caps">UNIQUE INDEX</span> on <code>subdomain</code>.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">AddIndexToAccountSubdomain</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Migration</span>
<span class="keyword">def </span><span class="method">self.up</span>
<span class="ident">add_index</span> <span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:unique</span> <span class="punct">=></span> <span class="constant">true</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">self.down</span>
<span class="ident">remove_index</span> <span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<p>Okay, let’s migrate this bad boy.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">account</span><span class="punct">-</span><span class="ident">demo</span> <span class="punct">:</span> <span class="ident">rake</span> <span class="ident">db</span><span class="symbol">:migrate</span>
<span class="punct">==</span> <span class="constant">AddIndexToAccountSubdomain</span><span class="punct">:</span> <span class="ident">migrating</span> <span class="punct">=====================================</span>
<span class="punct">--</span> <span class="ident">add_index</span><span class="punct">(</span><span class="symbol">:accounts</span><span class="punct">,</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="punct">{</span><span class="symbol">:unique=</span><span class="punct">></span><span class="constant">true</span><span class="punct">})</span>
<span class="punct">-></span> <span class="number">0.0047</span><span class="ident">s</span>
<span class="punct">==</span> <span class="constant">AddIndexToAccountSubdomain</span><span class="punct">:</span> <span class="ident">migrated</span> <span class="punct">(</span><span class="number">0.0050</span><span class="ident">s</span><span class="punct">)</span> <span class="punct">============================</span> </code></pre></div>
<p>Great, we’re now ready to move on to the fun stuff.</p>
<p>Let’s open up <code>app/models/account.rb</code> and throw some sugar in it.</p>
<h3>Data Validation</h3>
<p>Because we’re going to be dealing with subdomains, we need to make sure that we’re only allowing people to sign-up with valid data otherwise, there could be issues. URLs need to fit within certain conventions and we need to make it as graceful as possible for our customers.</p>
<p>Let’s make a quick list of what we need to enforce for the <code>subdomain</code> attributes. This can easily be expanded, but let’s cover the basics.</p>
<ul>
<li>Each account should have a <code>subdomain</code> </li>
<li>Each <code>subdomain</code> should be unique within the application</li>
<li>A <code>subdomain</code> should be alpha-numeric with no characters or spaces with the exception of a dash (my requirement)</li>
<li>A <code>subdomain</code> should be stored as lowercase</li>
</ul>
<p>So, let’s update the following default Account model….</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="keyword">end</span> </code></pre></div>
<p>..and add some basic validations.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">validates_presence_of</span> <span class="symbol">:subdomain</span>
<span class="ident">validates_format_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:with</span> <span class="punct">=></span> <span class="punct">/</span><span class="regex">^[A-Za-z0-9-]+$</span><span class="punct">/,</span> <span class="symbol">:message</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">The subdomain can only contain alphanumeric characters and dashes.</span><span class="punct">',</span> <span class="symbol">:allow_blank</span> <span class="punct">=></span> <span class="constant">true</span>
<span class="ident">validates_uniqueness_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:case_sensitive</span> <span class="punct">=></span> <span class="constant">false</span>
<span class="ident">before_validation</span> <span class="symbol">:downcase_subdomain</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">downcase_subdomain</span>
<span class="constant">self</span><span class="punct">.</span><span class="ident">subdomain</span><span class="punct">.</span><span class="ident">downcase!</span> <span class="keyword">if</span> <span class="ident">attribute_present?</span><span class="punct">("</span><span class="string">subdomain</span><span class="punct">")</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<h4>Reserved subdomains</h4>
<p>In the project that our team is working on, we wanted to reserve several subdomains so that we could use them later on. We tossed in the following validation as well.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">validates_exclusion_of</span> <span class="symbol">:subdomain</span><span class="punct">,</span> <span class="symbol">:in</span> <span class="punct">=></span> <span class="punct">%w(</span><span class="string"> support blog www billing help api </span><span class="punct">),</span> <span class="symbol">:message</span> <span class="punct">=></span> <span class="punct">"</span><span class="string">The subdomain <strong>{{value}}</strong> is reserved and unavailable.</span><span class="punct">"</span></code></pre></div>
<p>This will prevent people from using those when they sign up.</p>
<h3>Controller / Handling Requests</h3>
<p>Let’s now think about how we’ll handle requests so that we can scope the application to the current account when a subdomain is being referenced in the <span class="caps">URL</span>.</p>
<p>For example, let’s say that our application is going to be: <code>http://purplecowapp.com/</code> [1]</p>
<p>Customers will get to sign-up and reserve <code>http://customer-name.purplecowapp.com/</code>. I want my account subdomain to be <code>green.purplecowapp.com</code> and everything under this subdomain should be related to my instance of the application.</p>
<p>I’ve begun working on my own module, which is inspired mostly by <a href="http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb">the account_location plugin</a> with some additions to meet some of our product’s requirements.</p>
<p>Here is my attempt to simplify it for you (removed some other project-specific references) and <a href="http://gist.github.com/45826">have put this into a Gist for you</a>.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="comment">#</span>
<span class="comment"># Inspired by</span>
<span class="comment"># http://dev.rubyonrails.org/svn/rails/plugins/account_location/lib/account_location.rb</span>
<span class="comment">#</span>
<span class="keyword">module </span><span class="module">SubdomainAccounts</span>
<span class="keyword">def </span><span class="method">self.included</span><span class="punct">(</span> <span class="ident">controller</span> <span class="punct">)</span>
<span class="ident">controller</span><span class="punct">.</span><span class="ident">helper_method</span><span class="punct">(</span><span class="symbol">:account_domain</span><span class="punct">,</span> <span class="symbol">:account_subdomain</span><span class="punct">,</span> <span class="symbol">:account_url</span><span class="punct">,</span> <span class="symbol">:current_account</span><span class="punct">,</span> <span class="symbol">:default_account_subdomain</span><span class="punct">,</span> <span class="symbol">:default_account_url</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="ident">protected</span>
<span class="comment"># TODO: need to handle www as well</span>
<span class="keyword">def </span><span class="method">default_account_subdomain</span>
<span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_url</span><span class="punct">(</span> <span class="ident">account_subdomain</span> <span class="punct">=</span> <span class="ident">default_account_subdomain</span><span class="punct">,</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="ident">http_protocol</span><span class="punct">(</span><span class="ident">use_ssl</span><span class="punct">)</span> <span class="punct">+</span> <span class="ident">account_host</span><span class="punct">(</span><span class="ident">account_subdomain</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_host</span><span class="punct">(</span> <span class="ident">subdomain</span> <span class="punct">)</span>
<span class="ident">account_host</span> <span class="punct">=</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="ident">account_host</span> <span class="punct"><<</span> <span class="ident">subdomain</span> <span class="punct">+</span> <span class="punct">'</span><span class="string">.</span><span class="punct">'</span>
<span class="ident">account_host</span> <span class="punct"><<</span> <span class="ident">account_domain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_domain</span>
<span class="ident">account_domain</span> <span class="punct">=</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="ident">account_domain</span> <span class="punct"><<</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">domain</span> <span class="punct">+</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">port_string</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">account_subdomain</span>
<span class="ident">request</span><span class="punct">.</span><span class="ident">subdomains</span><span class="punct">.</span><span class="ident">first</span> <span class="punct">||</span> <span class="punct">'</span><span class="string"></span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">default_account_url</span><span class="punct">(</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="ident">http_protocol</span><span class="punct">(</span><span class="ident">use_ssl</span><span class="punct">)</span> <span class="punct">+</span> <span class="ident">account_domain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_account</span>
<span class="constant">Account</span><span class="punct">.</span><span class="ident">find_by_subdomain</span><span class="punct">(</span><span class="ident">account_subdomain</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">http_protocol</span><span class="punct">(</span> <span class="ident">use_ssl</span> <span class="punct">=</span> <span class="ident">request</span><span class="punct">.</span><span class="ident">ssl?</span> <span class="punct">)</span>
<span class="punct">(</span><span class="ident">use_ssl</span> <span class="punct">?</span> <span class="punct">"</span><span class="string">https://</span><span class="punct">"</span> <span class="punct">:</span> <span class="punct">"</span><span class="string">http://</span><span class="punct">")</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<p><a href="http://gist.github.com/45826">View gist here</a> (embed wasn’t working right when I tried)</p>
<p>Just include this into your <code>lib/</code> directory and <code>require</code> it in <code>config/environment.rb</code>. (if people think it’s worth moving into a plugin, I could do that)</p>
<h4>Including AccountSubdomains</h4>
<p>In the main application controller (<code>app/controllers/application.rb</code>), just include this submodule.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">include</span> <span class="constant">SubdomainAccounts</span>
<span class="punct">...</span>
<span class="keyword">end</span> </code></pre></div>
<p>Now, we’ll want to add a check to verify that the requested subdomain is a valid account. (our code also checks for status on paid memberships, etc… but I’ll just show a basic version without that)</p>
<p>Let’s add in the following to <code>app/controllers/application.rb</code>. This will only check on the status of the account (via subdomain) if the current subdomain is not the default. For example: <code>purplecowapp.com</code> is just our promotion site, so we won’t look up the account status and/or worry about the subdomain. Otherwise, we’ll check on the status.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="ident">before_filter</span> <span class="symbol">:check_account_status</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">check_account_status</span>
<span class="keyword">unless</span> <span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="comment"># TODO: this is where we could check to see if the account is active as well (paid, etc...)</span>
<span class="ident">redirect_to</span> <span class="ident">default_account_url</span> <span class="keyword">if</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">nil?</span>
<span class="keyword">end</span>
<span class="keyword">end</span> </code></pre></div>
<h4>Current Account meets Project model</h4>
<p>When requests are made to an account’s subdomain, we want to be able to scope our controller actions.</p>
<p><strong><span class="caps">WARNING</span></strong>: I’m going to gloss over the following steps because this is just standard Rails development stuff and I want to focus on how to scope your Rails code to account subdomains.</p>
<p>I’ll just say that this product gives each account many projects to do stuff within. I’ll assume that you’ll know how to handle all that and we’ll assume you have a Project model already.</p>
<p>What you will need is to add a foreign key to your table (projects in this example) that references Account. So, make sure that your model has an <code>account_id</code> attribute with and that the database table column has an <span class="caps">INDEX</span>.</p>
<p>We’ll add our associations in the models so that they can reference each other.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="comment"># app/models/account.rb</span>
<span class="keyword">class </span><span class="class">Account</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">has_many</span> <span class="symbol">:projects</span>
<span class="comment"># ...</span>
<span class="keyword">end</span>
<span class="comment"># app/models/project.rb</span>
<span class="keyword">class </span><span class="class">Project</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
<span class="ident">belongs_to</span> <span class="symbol">:account</span>
<span class="comment"># ...</span>
<span class="keyword">end</span> </code></pre></div>
<p>Okay great… back to our controllers. The SubdomainAccounts module provides you with the <code>current_account</code> variable, which you can use within your controllers/views. This allows us to do the following in our controllers. For example, if we had a ProjectsController.</p>
<div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">class </span><span class="class">ProjectsController</span> <span class="punct"><</span> <span class="constant">ApplicationController</span>
<span class="keyword">def </span><span class="method">index</span>
<span class="attribute">@projects</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:all</span><span class="punct">)</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">new</span>
<span class="attribute">@project</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">new</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">show</span>
<span class="attribute">@project</span> <span class="punct">=</span> <span class="ident">current_account</span><span class="punct">.</span><span class="ident">projects</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="ident">params</span><span class="punct">[</span><span class="symbol">:id</span><span class="punct">])</span>
<span class="keyword">end</span>
<span class="comment"># ...</span>
<span class="keyword">end</span></code></pre></div>
<p>See, this wasn’t so hard, was it?</p>
<h2>Handling layouts</h2>
<p>I wanted to highlight one other thing here because I suspect that most projects that fit this will likely need a promotional/resource site where people will sign-up from. In our application, we have two application layouts. One for the main application that customers will interact with via their subdomain and the promotional site layout.</p>
<p>The default layout is just <code>app/views/layouts/application.html.erb</code> and we have our promotional site layout at <code>app/views/layouts/promo_site.html.erb</code>. A few of our controllers are specifically for the promotional site while the rest are for the application itself and in some cases, there is some overlap down to individual action within a controller.</p>
<p>What we did was add a few more before filters to our application controller to a) define the proper layout to render, and b) skip login_required on the promo site.</p>
<p>To have the proper layout get rendered, we’re just checking whether the current request was made to the promotional site or not.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ...</span>
<span class="ident">layout</span> <span class="symbol">:current_layout_name</span> <span class="comment"># sets the proper layout, for promo_site or application</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">promo_site?</span>
<span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_layout_name</span>
<span class="ident">promo_site?</span> <span class="punct">?</span> <span class="punct">'</span><span class="string">promo_site</span><span class="punct">'</span> <span class="punct">:</span> <span class="punct">'</span><span class="string">application</span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="comment"># ...</span>
<span class="keyword">end</span></code></pre></div>
<p>Our application is using Restful Authentication and we just want to check to see if the current request is made to the promotional site or not. If it is, we’ll skip the <code>login_required</code> filter. Let’s assume that you have the following before_filter set.</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ...</span>
<span class="ident">before_filter</span> <span class="symbol">:login_required</span></code></pre></div>
<p>We’ll just change this to:</p>
<div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">ApplicationController</span> <span class="punct"><</span> <span class="constant">ActionController</span><span class="punct">::</span><span class="constant">Base</span>
<span class="comment"># ..</span>
<span class="ident">before_filter</span> <span class="symbol">:check_if_login_is_required</span>
<span class="ident">protected</span>
<span class="keyword">def </span><span class="method">promo_site?</span>
<span class="ident">account_subdomain</span> <span class="punct">==</span> <span class="ident">default_account_subdomain</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">current_layout_name</span>
<span class="ident">promo_site?</span> <span class="punct">?</span> <span class="punct">'</span><span class="string">promo_site</span><span class="punct">'</span> <span class="punct">:</span> <span class="punct">'</span><span class="string">application</span><span class="punct">'</span>
<span class="keyword">end</span>
<span class="keyword">def </span><span class="method">check_if_login_is_required</span>
<span class="ident">login_required</span> <span class="keyword">unless</span> <span class="ident">promo_site?</span>
<span class="keyword">end</span>
<span class="comment"># ...</span></code></pre></div>
<p>There we go. We can now render the proper layout given the request and only handle authentication when necessary.</p>
<h2>Development with account subdomains</h2>
<p>When you begin developing an application like this, you need to move beyond using <code>http://locahost:3000</code> as we need to be able to develop and test with subdomains. You can open up your <code>/etc/hosts</code> (within a Unix-based O/S) file and add the following.</p>
<pre><code>
127.0.0.1 purplecowapp.dev
127.0.0.1 green.purplecowapp.dev
127.0.0.1 sample.purplecowapp.dev
127.0.0.1 planetargon.purplecowapp.dev
127.0.0.1 lollipops.purplecowapp.dev
127.0.0.1 help.purplecowapp.dev
127.0.0.1 support.purplecowapp.dev
</code></pre>
<p>After you edit that file (with root permissions), you can flush your dns cache with <code>dscacheutil -flushcache</code> (Mac <span class="caps">OS X</span>). This will let you make requests to <code>http://purplecowapp.dev:3000/</code> and <code>http://green.purplecowapp.dev:3000</code>. This is a convention that our team has begun using for our own projects (TLD ending in <code>.dev</code>). It’s important to remember that the subdomain must be specified here in order to work for local requests. Unfortunately, hosts files don’t support wildcards (’*’).</p>
<h3>Update</h3>
<p>You can also use Ghost, which is a gem for managing <span class="caps">DNS</span> entries locally with Mac <span class="caps">OS X</span>. Read <a href="http://www.robbyonrails.com/articles/2009/01/12/get-to-know-a-gem-ghost">Get to know a gem: Ghost</a></p>
<h2>Summary</h2>
<p>I know that I glossed over some sections, but was hoping that the code itself would be the most beneficial for you. Feel free to leave any questions and/or provide some feedback on our approach. Perhaps you have some suggestions that I could incorporate into this so that we can improve on this pattern.</p>
<p id="fn1"><sup>1</sup> yeah, I’ve been reading more Seth Godin recently…</p>