Feb 04 2009
Use Rails to Create a Static Site
One of the new things we released last week with dradis v2.0 was a new web site for the project (dradis.sourceforge.net).
The old site consisted of 20 static pages or so, which was nice and easy but a real pain to maintain or restructure. So we thought that letting Rails do the heavy lifting for us would be a good idea, but we did not want to set up a Rail environment in the server…
What we finally did is use Rails as a tool to create a static site that we could .tar.gz and upload to the server. As a starting point we used a post in www.chuckvose.com and this is how we completed it to fit our needs.
Follow up (2009-03-23): do not miss how to integrate your rails-static site with Rake and Subversion in the second article of this series: Use Rails to Create a Static Site: Rake and Subversion.
The Controller
We just need one action in our main controller (PagesController):
class PagesController < ApplicationController layout 'main' def index @page = params.fetch(:id, 'index') expanded_page = "#{RAILS_ROOT}/app/views/pages/#{@page}.html.erb" exists = File.exists?( File.expand_path(expanded_page)) if exists render :action => @page else render :text => "#{expanded_page} doesn't exist" end end end
We fetch the :id parameter from the request that contains the name of the requested page (or index by default), then we try to locate the corresponding file (in /app/views/pages/) and if found we render the action. Rendering the action with no action code in the controller will just render it’s ERB template which is exactly what we want.
We also added a couple of routes in /config/routes.rb so every time a something.html was requested, the PagesController would be invoked:
map.root :controller => 'pages' map.connect ':id.html', :controller => 'pages', :action => 'index'
The Layout and the Pages
The layout we used was quite simple. The most interesting bits follow.
First, we want each page to be able to set their own HTML title. This is accomplished by including the following code in the layout:-
<head> <title><%= yield :title %></title> </head>
Then pages must include this line to set the contents of the :title variable:-
<% content_for :title do %>Announcements - dradis<% end %>
Other than that, we used the standard @content_for_layout in the main section of the page. However, the site has two menus, one in the top and one in the right hand side. This exposes a new challenge because we need to create the structure of the page in a way that is simple enough to maintain and add new content in the future. We had to code some Rails helpers to aid us in this.
The Helpers
The Top Menu
![]()
The Top menu bar consists only of the main sections of the site. In the layout we call the Helper straight away passing the current page (see The Controller above):
<div class="bar"> <ul> <%= barmenu(@page); %> </ul> </div>
The code of the helper in this instance is quite simple. The only intelligence we are adding is a check to see if the current page is one of the items in the menu, if it is, we add the CSS active class to the list item.
def barmenu(page) items = ['download', 'demo', 'screenshots', 'documentation' ] items.collect do |i| active = '' label = i.capitalize if (page == i) active = ' class="active"' else label = "<a href=\"#{i}.html\">#{i.capitalize}</a>" end "<li#{active}>#{label}</li>" end end
Right Hand Side Navigation

The navigation menu in the right side was a bit trickier. We had our pages organised in three different sections: using dradis, developing dradis and support from.
Each section contains a number of items, some of the items have our content, some are links to external sites. In addition to this it would be useful if we could nest subsections as shown in the screenshot of the right.
So we had to figure out a way of representing the structure of the menu. Probably the neatest solution would have been to create an HTML page that contained a series of nested ul and li elements and then make the helper parse that structure assign the active CSS class to the right element and display it. Or maybe using a fancy JavaScript trick to locate the active item and assign the CSS class. However, as often happens we needed a solution, and it had to be fast, so we opted for having the structure loaded in a ruby array with one Hash per section:
$sections = [ { ... }, # using dradis { :title => 'developing dradis', :pages => [ { :title => 'info. for developers', :url => 'developers.html', :pages => [ :extensions ] }, :roadmap, { :title => :bug_tracker, :url => 'http://sourceforge.net/tracker/?atid=1010917&group_id=209736&func=browse' }, { :title => :feature_requests, :url => 'http://sourceforge.net/tracker/?atid=1010920&group_id=209736&func=browse' }, { :title => :subversion, :url => 'http://sourceforge.net/svn/?group_id=209736' } ] }, { ... } # support from ]
Each of the three section Hashes has a :title and a :pages attribute. The latter is an array containing the information of the pages associated with the parent section. The elements of the array can be either Symbols or new Hashes that describe the different pages.
We use a Symbol if the content of the page is provided by the site such as in the :roadmap case whose contents are in /app/views/pages/roadmap.html.erb and the link displayed in the menu would be “roadmap”. In more complex cases (i.e. we want a custom title for the link or a section that contains subsections) we use a Hash.
When a Hash is used at least a :title and a :url elements must be provided. Optionally a :pages element can be used to nest sections inside sections. The format of this :pages value is again an array as the one used in the top level section. So there you have your recursion!
The helper in this case is a bit more complex. First, the call in the layout, nothing fancy here:
<div> <%= rightmenu(@page) %> </div>
The rightmenu() helper code:
def rightmenu(page) $sections.collect do |section| "<h3>#{section[:title]}:</h3>" + sectionmenu(:page => page, :section => section) end end
Each main section is enclosed in h3 tags and then sectionmenu (a helper-to-the-helper method
) is invoked for each section. There are some tricks going on in the following piece of code, have a look and we will nail it down later:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | def sectionmenu(options = {}) current_page = options[:page] section = options[:section] level = options.fetch(:level, 0) css = options.fetch(:css, ['right_articles']) css |= ["submenu#{level}"] if (level > 0) section[:pages].collect do |page| # If the last item was the active one, clear the flag css.delete('active') if css.include?('active') title = '' url = '' complex = false # simple page (Symbol) vs. complex page (Hash) if (page.class == Hash) complex = true title = page[:title].to_s.gsub(/_/,' ') url = page[:url] else title = page.to_s.gsub(/_/,' ') url = page.to_s + '.html' end # active vs. inactive if (current_page + '.html' == url) css << 'active' else title = "<a href=\"#{url}\">#{title}</a>" end # subsections # tradeoff: calculate the sub section, # if it doesn't contain the "active" page, don't use it subsection = '' if (complex && page.key?(:pages)) subsection = sectionmenu( :page => current_page, :section => page, :level => level + 1 ) found = !(/active/ =~ subsection).nil? if ( !found && !css.include?('active') ) subsection = '' end end "<div class=\"#{css.join(' ')}\">#{title}</div>" + subsection end.join end |
In lines 2 to 6 we initialise the properties for this section. Then for each page in the section we apply the loop in 8 to 51. Due to the way the data is structured in the $sections variable, this code can be used to recursively crawl the tree of sections and subsections.
For each page in the section:
- we initialise the internal variables (9 to 13). If the page is a
Hashwe extract the title and URL for the link from it’s elements, if it is aSymbolwe infer the title and link from the symbol name. - Then we check if the current page in the iteration is the active one (26 to 30). If it is, we add a CSS attribute to the item, if it is not, then we add a hyper-link to it (it would not make sense to have a link to the page if you are already in it).
- Finally in 36 to 47 we check if there is a :pages element in the
Hashand if there is one, we make a recursive call to render the subsection.
Two things need to be said about the above piece of code. First, each subsection will be assigned a CSS class in accordance to it’s level: submenu1, submenu2, etc. This will allow us to style the different subsections appropriately. And second, because there is no easy way of knowing if the active belongs to one of the sub sections of the current section we make the recursive call, and then see if we have found the active page. If we have not, we discard the subsection altogether. We want this behaviour of not displaying a subsection in the menu unless the user is already in that section and this was the easiest way of implementing it.
The Bread Crumbs

The bread crumbs help our users to know where they are in the site (see picture in the right sid). We need to have a flexible helper that would display the path the user has followed to get into the page they are at the moment. Below is the code of the layout, just above the main content and with a check to verify that we are not in the main page:
<div class="left"> <% if @page != 'index' %> <div id="breadcrums"> <ul> <%= breadcrumsmenu(:page => @page) %> </ul> </div> <% end %> <%= @content_for_layout %> </div>
And again, this helper will use the $sections as discussed above.
def breadcrumsmenu(options={}) crums = [] $sections.each do |section| options[:section] = section crums = breadcrums(options) break if (crums.size >1) end this_page = crums.pop (crums.collect do |c| "<li><a href=\"#{c[:url]}\">#{c[:title]}</a></li>" end).join + "<li>#{this_page[:title]}</li>" end
Again a similar tradeoff here, we cycle through all the sections trying to find the active page. We use the breadcrums helper-to-the-helper function to find out if the page is in the given section and then we render the bread crumbs. I realise that the two helper-to-the-helper functions are quite similar (cycle through a section trying to locate the active page), and possibly we could refactor both into a single function that does thing. But then again, once it worked, we did not have too much spare time to look into it, so we may improve it in the future, but this is the solution we came up with at that point. The code of breadcrums() is very similar in structure to the code in sectionmenu() but this time, it returns an array of pages that will lead us to the active one:
def breadcrums(options={}) current_page = options[:page] section = options[:section] crums = options.fetch( :crums, [ {:title => 'home', :url => '/'} ]) section[:pages].each do |page| title = '' url = '' complex = false found = false if (page.class == Hash) complex = true title = page[:title].to_s.gsub(/_/,' ') url = page[:url] else title = page.to_s.gsub(/_/,' ') url = page.to_s + '.html' end if (current_page + '.html' == url) found = true crums << { :title => title, :url => url } end if (!found && complex && page.key?(:pages)) subsectioncrums = breadcrums( :page => current_page, :section => page, :crums => [{:title => title, :url => url}] ) if (subsectioncrums.size > 1) crums += subsectioncrums end end break if (crums.size > 1) end return crums end
Finishing Touch
Finally, to generate the static pages that we could compress and upload to the server, some good old wget magic was used:
<br /> wget -m -nH http://localhost:3000/<br />
Popularity: 75% [?]

February 4th, 2009 at 6:48 pm
Did you try tools like staticmatic or webby? It think, it may be some simpler, than using rails for this task.
February 4th, 2009 at 7:03 pm
I didn’t try them because at the time they seemed to be a bit overkilling. However, I have to be honest and when I first thought about it I didn’t realise that the recursive section thing was going to be so *interesting*. Do you know if it is possible to have this kind of behaviour with webby?
February 4th, 2009 at 10:45 pm
Hmmm.. I don’t know any built-in support for recursive sections in webby or staticmatic. It’s simply generates set of pages based on templates and some helpers.
Possible, some third-party helpers implementing such behavior exist.
February 5th, 2009 at 5:12 am
Hi,
Thanks for the tutorial. I can learn more from this tutorial.
February 6th, 2009 at 12:00 am
Your welcome!
March 23rd, 2009 at 12:31 pm
[...] have already seen how to Use Rails to Create a Static Site. In that article we left the site running, and we recommended the use of wget to generate the [...]
June 12th, 2009 at 1:42 am
Is there any source code which includes all these cool things as a sample?
I tried to get the latest version of dradis and I can’t find most of the things.
As a new developer with Ror (but not a new developer at all) I need such a sample very much.
Thanks in advance
June 12th, 2009 at 1:51 am
This code is used to handle the website of dradis (http://dradisframework.org/), it is not in dradis itself
However I see that a small example would be useful, I will try to work on it as soon as I get the time!
June 12th, 2009 at 11:04 pm
Thanks a bunch.
I’m really eager to start RoRing