Skinning your entire Rails app

Posted by unixmonkey on September 18, 2007

Say I want to deploy my app as a service to several good customers, but I want my app to share the exact layout as each customer site, so it appears to be an internal app to their company and its own clients. I take thier CSS and HTML markup, and make it into a template to wrap around my app’s content.

When I took to task to replicate that functionality in Ruby on Rails, I wasn’t certain would be easily accomplished. All the tutorials I’ve read list template switching as purely CSS affairs. Using CSS to change the presentation of your site is a fine thing, but isn’t enough if you are looking to completely copy the look and feel of a client’s website with your app dropped in.

It took some thought and a little help from the community.

Previously, the system I was used to accepted a site variable passed with the login form that told which template to render. like

That’s ugly and really obvious to what it is doing, but what other way is there to know which template to render?

I recall setting up accounts at a handful of sites that were in the format of:

This is known as using a subdomain as an account key. Luckily, there is a very simple plugin to set that up in Rails

After setting up the plugin, I’m able to access the subdomain name anywhere in the app. Good thing, because I need to access it in application.rhtml (or .haml)

<% # if there's a subdomain, render the partial of the same name -%>
<% if account_subdomain -%>
    <% # first check to make sure account_subdomain is valid and in accounts table  -%>
    <% @account = Account.find_by_username(account_subdomain) -%>
    <% unless @account.nil? -%>
        <%= render :partial => "layouts/"+account_subdomain %>
    <% else -%>
        <% # There's a subdomain, but it isn't valid. Render default template -%>
        <%= render :partial => "layouts/default" %>
    <% end -%>
<% else -%>
    <%= render :partial => "layouts/default" %>
<% end -%>

Notice in the above, that there are no :yield statements. That’s because :yield can live in a partial too. I’ve set up a partial for each subdomain listed in my database in app/views/layouts, and added some logic to render the default layout when a subdomain either isn’t present or is invalid.