Rails theme support with Metal
Metal is neat. Themes are neat. Together they are nifty. So, in the spirit of the upcoming St. Patrick’s day holiday I decided to play around with metal to add theme support to my rails app. I like how Wordpress supported themes by having meta data tucked into the stylesheet itself, and I also like query string (odd, I know) — so I figured I’d melt them together… in the spirit of the holiday and all.
The Theme Files
First things first. We need to create a themes folder. I chose RAILS_ROOT/public/stylesheets/themes but anywhere public would work. Inside there I threw the following stylesheet:
I made the theme support a minimum of 3 meta fields:
- theme — The name of the theme
- begin — What date to start using the theme
- end — What date to stop using the theme
- *enabled — Optional: If you want to enable this theme
You can hack on this and include as many as you want — they follow the same basic format as URLs:
theme.csstheme=My Themes Name&begin=March 17&end=March 18
/*
RAILS_ROOT/public/stylesheets/themes/st_patricks_day.css
{{
theme=st patricks day
&begin=March 17
&end=March 18
&enabled=1
}}
*/
#header{
background:#C2DDCA url(theme_images/shamrock.png) no-repeat 25px 20px;
padding-left:50px;
}
The Metal
And, now into the neat part. Generate a rails metal file to handle theme switching on the fly, according to the current date.
$>./script/generate metal theme
Which should give you a nice app/metal/theme.rb file which you can play with.
I hacked together this code — and I’m not sure how stable it is, but should probably used in production with caution. It uses Rails.cache so make sure in your environment.rb file you have a cache store set.
class Theme
# this is where the theme index is stored
CacheKey = 'themes'
# this is what we will look for in the layout
ThemeEnvKey = 'rails.theme'
# this is the path to your themes folder
ThemesPath = File.join(RAILS_ROOT, 'public', 'stylesheets', 'themes')
def self.call(env)
# Build our theme index
themes = Rails.cache.fetch(CacheKey) do
data = {}
Dir[File.join(ThemesPath, '*.css')].each do |theme|
# looks for {{ url_type_string_here }} comment meta data and parses
# it out into a hash for the theme's file name
#
# within your theme file, you would have:
# {{theme=my name&begin=DATE&end=DATE}}
# becomes
# {'theme.css' => {:theme => 'my mane', :begin => 'START_DATE', :end => 'FINISH_DATE'}}
#
# DATE is parsed so it can be any type of
# date-ish string see ActiveSupport::TimeZone#parse
begin
data[File.basename(theme)] = \
$1.split('&').inject({}) do |h, v|
s = v.split('=').map(&:strip)
h[s.first.to_sym] = s.last
h
end if File.read(theme) =~ /\{\{(.+)\}\}/m
rescue Exception => e
data[theme] = {:error => e.to_s}
end
end
data
end
# Set the appropriate theme
now = Time.zone.now.beginning_of_day
themes.each_pair do |file, data|
next if data.keys.include?(:enabled) and data[:enabled].to_i.zero?
next unless data.keys.include?(:begin) and data.keys.include?(:end)
if Time.zone.parse(data[:begin]) <= now and Time.zone.parse(data[:end]) > now
env[ThemeEnvKey] = file
break
end
end
# pass
Rails::Rack::Metal::NotFoundResponse
end
end
What this basically does is:
- Check within the ThemesPath for any .css files
- Reads them and parses out the metadata within the stylesheet
- Caches that
- Checks the date to see if any theme files should be turned on
- Sets an env key to let the rest of Rails know we want a theme
The Rails Layout
Well, now Metal has set our env['rails.theme'] key so we know a theme is in order it is time to put that theme to use.
# your applications main layout file (app/views/layouts/application.html.erb?)
# I put this after the normal stylesheet call, so our theme can override what we want instead of the entire stylesheet
<% if theme = request.env['rails.theme'] %>
<%= stylesheet_link_tag "themes/#{theme}", :media => :all %>
<% end %>
Once all these steps are combined, and March 17th hits, we should now have a stylish header image that shows the shamrock.png! I can almost hear the users clinging glasses







Add Yours
YOU