Saturday, December 31, 2005

2. Routing and Creating an index page for the app

Now we need to create a Landing page (the front page for the website) for the app. Let us take some time and understand how routing works in ROR. Also, check out more info on routing here and here.
  1. URL routing to controllers and associated actions is controlled by demo/config/routes.rb:
    ActionController::Routing::Routes.draw do |map|
    # Add your own custom routes here.
    # The priority is based upon order of creation: first created -> highest priority.

    # Here's a sample route:
    # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
    # Keep in mind you can assign values other than :controller and :action

    # You can have the root of your site routed by hooking up ''
    # -- just remember to delete public/index.html.
    # map.connect '', :controller => "welcome"

    # Allow downloading Web Service WSDL as a file with an extension
    # instead of a file named 'wsdl'
    map.connect ':controller/service.wsdl', :action => 'wsdl'

    # Install the default route as the lowest priority.
    map.connect ':controller/:action/:id'
    end
    The entry in bold is the default route. It will extract the controller and action (and id) from the url and invoke the corresponding method in the correspnding controller class.

  2. Now suppose you invoke a url to an unknown controller such as http://127.0.0.1:3000/asdf.
    You should get something like

    Rails supports the concept of environments. Based on whether the server is in the development mode or production mode, 404 and 500 http errors are displayed differently.

    In the production mode, ROR will display the public/404.html automatically for the above url (you will need to restart the ruby server in the production mode by specifying ruby script/server -r prodcution and hitting the non-existent controller from a browser on a different machine).

    But for sake of understanding how routing works, let's say we want to display the same page for 404 Not Found error whether we are in the production mode or development mode.

    We will add a default catch-all to trap all accesses to non-existent controllers:

    • Create a controller to generate a Unknown request page.
      cd demo; ruby script/generate controller index unknown_request

    • Add the HTTP 404 status to the render method in demo/app/controllers/index_controller.rb
      class IndexController <>
      def unknown_request
      render(:status => "404 Not Found")
      end
      end


    • Add the following as the last entry to routes.rb:
      # Add default catch-all. Should display a error page and allow the user
      # to click back to main page
      map.connect '*all', :controller => 'index', :action => 'unknown_request'

      Note: Is is possible for ruby routing to map to a static html file? I didn't see any examples anywhere. If my error page is a static page, to avoid a resource hit on the framework, I should be able to point directly to a static html file through routes.rb.

    • Update app/views/index/unknown_request.rhtml
      <h1>Error: Page not found!</h1>
      <p>Please click <%= link_to("here", url_for(:action => "index")) %> to return to the main page.</p>
      We will add the index action to the index controller in a minute.

    Now if you were to invoke the same url you should get the unknown_request page in both production and development modes.

    You should also know about rescue_action_in_public and other ideas here though I wasn't able to trap ActionController::RoutingError using this method. Again this method will render differently based on whether the server is running in the production mode or development mode.

  3. With access to non-existant controllers controlled, say you also want to control the actions that are allowed for a controller. You can use something like
    map.connect 'user/:action/:id',
    :controller => "user",
    :requirements => { :action => /(new|edit|show)/ },
    :id => nil

    Note that with this added to to the routes.rb file, you won't be able to generate urls for the disallowed actions such as destroy and list using the url_for method and you would need to remove all such references from your user_controller.rb and views.
Now that we know a little bit about routing, let's add a index page to our demo app.
  1. The default index.html that you see when you navigate to http://127.0.0.1:3000 is in
    demo/public/index.html. You need to rename it to something else before your changes for / in routes.rb will be seen by Rails (Does ROR cache the file existance test for public/index.html in production mode?). If you have a static landing page, you can simply edit public/index.html for your content and use it.

  2. If the main page needs to serve dynamic content, then you will need to delete or rename demo/public/index.html and add a route to demo/config/routes.rb.

    We will add a controller that will display a dynamic index page.

    • Add a route to '/' and other variants of the main index page in config/routes.rb:
      ActionController::Routing::Routes.draw do |map|

      ...
      # Add index page controller
      map.connect '', :controller => 'index', :action => 'index'
      map.connect 'index', :controller => 'index', :action => 'index'
      map.connect 'index.htm', :controller => 'index', :action => 'index'
      map.connect 'index.html', :controller => 'index', :action => 'index'


    • If you haven't already created a controller called index (as instructed above), create the index controller. Update demo/app/controllers/index_controller.rb to add a new action called index:
      class IndexController <>
      def index
      end
      ...
      end

    • Update demo/app/views/index/index.rhtml with your content:
      <h1>Welcome to my demo website</h1>
      <p>Add content here</p>
Navidate to http://127.0.0.1:3000 and you should see your new index page.

You can use routes.rb similarly for bringing down the website for maintainance. For e.g. say if you want all website links to point to a special maintainance page, you would do the following:
    • Create a maintainance controller with an index action:
      cd demo

      ruby script/genenrate controller maintainance index
      exists app/controllers/
      exists app/helpers/
      create app/views/maintainance
      exists test/functional/
      create app/controllers/maintainance_controller.rb
      create test/functional/maintainance_controller_test.rb
      create app/helpers/maintainance_helper.rb
      create app/views/maintainance/index.rhtml

    • Add the following route to the config/routes.rb file at the top:

      ActionController::Routing::Routes.draw do |map|

      # Add Landing page controller
      map.connect '*all', :controller => 'maintainance', :action => 'index'
      ...
      end

    • Update demo/app/views/maintainance/index.rhtml with your content:

      <h1>Website Down for maintainance</h1>

      <p>Please visit us tomorrow!</p>

    Now all links on your website will go to this page. In future to bring the website down for maintainance, you just need to edit config.rb and add the route and restart the webserver ( I am guessing in the production mode, routes.rb contents are read at startup and cached).