Static content in Rails 3

January 30, 2011Posted by Tom Meier

To auto-generate static pages in Rails 3, other than by simply adding html files to the ‘public’ directory, is very easy.

The most simple way is to just; add a controller such as ‘PagesController’, redirect all unmatched routes there, create a view and it will be displayed if the html/haml view exists – with any others raising a template not found error (display 500 to the production user).

However, I usually want to capture whats going on behind the scenes, or share variables across a group of pages.

Disclaimer – this isn’t the ideal or the best way, but it is my convenient way, let me know if you have any tips ’n tricks to improve it further!

Static Schoolgirls

My usual setup can be handy, if you want custom static pages for both scoped areas of those logged in and those not, or even subdomains, anything is possible really with the Rails 3 routing setup. Simply add the following:

Create a pages controller class, under any submodule or folder structure you like.
This will simply check if the view template exists (even haml) and render that, otherwise render out your custom 404 page, the only params being sent are ‘base_page’ which will be set by the routes.

app/controllers/pages_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PagesController < ApplicationController

  def base_page_processor
    view_prefix = "pages"

    if params[:base_page].present? && template_exists?(params[:base_page], view_prefix)
      render "#{view_prefix}/#{params[:base_page]}"
    else
      #TODO : Notify missing url via email error or error notification service
      render '/public/404.html', :status => 404
    end
  end

end

To ensure all routes are matched, that aren’t specifically set in the config/routes.rb file, add this line right at the very end of the file (or at the end of a specific scope, such as a logged in section) :

config/routes.rb
1
match  ':base_page', :controller => 'pages', :action => 'base_page_processor', :as => :page_processor

Some specs to help you along the way (using RSpec), and ensure you don’t break the setup as time goes on:

spec/controllers/pages_controller_spec.rb
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
require 'spec_helper'

describe PagesController, 'automatic paths' do
  before(:all) do
    @routes = Rails.application.routes
  end

  it "should route all traffic and render if template exists" do
    ["/some_random_path", "/something_else", "/doesnt_exist"].each do |dynamic_path|
      @routes.recognize_path(dynamic_path).should == {:controller => 'pages', :action => 'base_page_processor', :base_page => dynamic_path.gsub(/^\//,'')}
    end
  end

  it 'should not route any traffic with sub paths' do
    ["/some_random_path/with_sub/lines", "/something_else/123/something.csv", "/doesnt_exist/nope"].each do |dynamic_path|
      lambda { @routes.recognize_path(dynamic_path) }.should raise_error(ActionController::RoutingError)
    end
  end

  describe 'with templates existing' do

    it 'should render the template' do
      controller.stub!(:template_exists?).and_return(true)
      controller.stub!(:render)

      controller.should_receive(:render).with( 'pages/something_that_exists' )

      get 'base_page_processor', :base_page => 'something_that_exists'

      response.status.should == 200
    end
  end

  describe 'when template doesnt exist' do

    it 'should render the 404 page' do
      get 'base_page_processor', :base_page => 'something_that_doesnt_exist'

      response.status.should == 404
      response.should render_template('public/404')
    end
  end

end

Now simply add any view template in your set page prefix, in the above example this would be ‘/app/views/pages/any_template_file’, this will now immediately load in your app. Easy.

Tagged ruby, rails, programming