+44 (0)1179 113711

Rock Solid Knowledge Blogs

An aggregation of all the Rock Solid Knowledge Blogs

Subscribe  Subscribe

Rails - Routing

It's been a while since my last post, that's a mixture of being at PDC and battling with Rails routing so that I understand it better and so that I can bend it to my will! I've finally got something working to my liking but it's taken a while.

Routing is a huge part of Rails and, like most things in Rails, I'm not going to be able to do it full justice in this post.

Rails provides a flexible and convenient routing mechanism, which sounds like marketing speak but is true. If the application uses RESTful routes then much of the work is done for it, although only if you want to work exactly the way Rails expects, otherwise you have to understand the routing to get it to fit into your scheme.

Routing information is stored in a file called routes.rb which is in the config directory. The route data 'draws' routes by mapping from resources to HTTP verbs/URLs and vice versa. This means that for a given HTTP verb (GET say) and a given URL ("/blog/foo") there is a map to a specific controller/action pair.

When a controller is generated from a script an entry is automatically placed in /config/routes.rb. For a 'blogs' controller the entry would look like this:

map.resources :blogs

This entry sets up a bunch of routes for the "blogs" controller in this application. There are several ways to see these routes. The generated BlogsController class will have a set of commented methods that show which URLs will be directed to which methods (shown here but shortened for convenience)

  # GET /blogs
  # GET /blogs.xml
  def index

  # GET /blogs/1
  # GET /blogs/1.xml
  def show

  # GET /blogs/new
  # GET /blogs/new.xml
  def new

  # GET /blogs/1/edit
  def edit

  # POST /blogs
  # POST /blogs.xml
  def create

  # PUT /blogs/1
  # PUT /blogs/1.xml
  def update

  # DELETE /blogs/1
  # DELETE /blogs/1.xml
  def destroy

However this does not tell the whole story, for example these routes have names and these names can be used in your code to add references to the URLs.

Another way to look at the roots available is to get Rails to list them. One way of doing this is to execute the rake routes command:

$ rake routes

                    blogs GET    /blogs                       {:controller=>"blogs", :action=>"index"}
          formatted_blogs GET    /blogs.:format               {:controller=>"blogs", :action=>"index"}
                          POST   /blogs                       {:controller=>"blogs", :action=>"create"}
                          POST   /blogs.:format               {:controller=>"blogs", :action=>"create"}
                 new_blog GET    /blogs/new                   {:controller=>"blogs", :action=>"new"}
       formatted_new_blog GET    /blogs/new.:format           {:controller=>"blogs", :action=>"new"}
                edit_blog GET    /blogs/:id/edit              {:controller=>"blogs", :action=>"edit"}
      formatted_edit_blog GET    /blogs/:id/edit.:format      {:controller=>"blogs", :action=>"edit"}
                     blog GET    /blogs/:id                   {:controller=>"blogs", :action=>"show"}
           formatted_blog GET    /blogs/:id.:format           {:controller=>"blogs", :action=>"show"}
                          PUT    /blogs/:id                   {:controller=>"blogs", :action=>"update"}
                          PUT    /blogs/:id.:format           {:controller=>"blogs", :action=>"update"}
                          DELETE /blogs/:id                   {:controller=>"blogs", :action=>"destroy"}
                          DELETE /blogs/:id.:format           {:controller=>"blogs", :action=>"destroy"}

The format is probably screwed as you look at this but hopefully it's still easy to work out what is going on. This lists the VERB/URL mapping to Controller/action, in this case the routes are only shown for the "blogs" resource, the actual listing is much longer and contains all resources as well as the default routing behaviour. The list is fairly straightforward, it shows the name of the route (if there is one), the HTTP verb, the URL and the controller/action these map to. For example the 'blog' named route says that sending a URL of the form blog/[:id] (e.g. /blogs/1) with the HTTP GET verb results in a call to the blogs controller's show method. In this case the value '1' at the end of the URL will be available as the :id value in the params collection. Notice that not all routes are /controller/action style, but that some simply rely on /controller and the HTTP verb to work out the method to call.

The names of the routes are very useful. For example in the above there are routes named 'blogs', 'blog' and 'new_blog' amongst others. These names can be used to display a URL for that route. As an example in views/blogs/index.html.erb there is a line like this:

<%= link_to 'New blog', new_blog_path %>

that says to use the path generated from the new_blog route to get the URL to display.

This is all well and good, and nice and easy to use and if you simply want to use the Rails conventions then you are good. However there are going to be occasions when simply using the defaults does not work. Luckily, for those cases the routing infrastructure is extensible. In the case of this blog I wanted to do two things, I wanted to support multiple blog authors, and I wanted blog IDs to be friendlier. In fact both these things are related, although I've only tackled the first issue at the moment, but the second issue is resolvable using a similar mechanism.

These issues are to do with the way that Rails identifies resources. It does this via the 'id' primary key. So to get to a blog you would send a GET to http://localhost/blogs/1 where blogs is the name of the controller and 1 is the id of the blog. This isn't ideal for a user. The id is an internal representation that the database uses to identify the blog. An end user doesn't want to say 'go to blog 1', they want to say "kevin's blog" or "harry's blog". To support that blogs should have 'nicknames' and these nicknames then get used to identify the blog, meaning that the url becomes http://localhost/blogs/kevin. Similarly with blog entries, the URL to the entry should include the nickname of the blog and maybe the title of the entry. So something like http://localhost/blog/kevin/my-title rather than http://localhost/blog_entry/1/1, where 'blog-entry' is the name of the controller, the first '1' is the id of the blog and the second '1' is the id of the entry. Fixing this requires several steps some of which I'll cover here. At this point I've not fully converted the code to produce URLs like the above but I'm a large step towards it. That step means not relying on the default routes drawn for REST based controllers and rewriting some of the usage of named routes in the controller and views.

The first thing I wanted to prevent was the user having to use the 'blog_entry' name for the controller. This is an internal representation and, as a user, I don't like the name. I wanted something shorter, so I've chosen 'blog' as the alias for the controller, you can guarantee that at some point in the future I'll have come up with something else! I also want to use a 'nickname' as an identifier to show whose blog entries we were looking at. This meant I wanted a URL something like http://localhost/blog/kevin to retrieve all blog entries for "kevin". I did this by modifying routes.rb.

In routes.rb I took out the entry for map.resources blog_entries and added explicit entries for routing. The entries look like this:

  map.blog_entries 'blog/:nickname',
            :controller => "blog_entries",
            :action => "index",
            :conditions => {:method => :get}

  map.connect 'blog/:nickname',
            :controller => "blog_entries",
            :action => "create",
            :conditions => {:method => :post}

  map.new_blog_entry 'blog/:nickname/new',
        :controller => "blog_entries",
        :action => "new",
        :conditions => {:method => :get}

There are many more entries and there are also also entries for formatted output (basically, I did a rake routes, copied and pasted the code and edited it). The above entries show a number of things: there are two named routes (blog_entries and new_blog_entry), and one unnamed route (the call to map.connect). As an aside notice the the named routes are named with a call to map.some_function. This function does not exist and shows the power of Ruby as a language. Internally the map class will override the method_missing method to add this named route. The routes specify the HTTP verb, the controller to call and the action to use on that controller. Notice that all the routes all specify an extra paramter, :nickname. This will be part of the URL and be passed into code in the params collection in much the same way that :id is passed. Once these routes have been defined you can then use them in code.

The nickname is associated with an individual blog and through that with the entries in that blog. This means that to use the nickname we always need to load the blog associated with it. In the BlogEntriesController class I added a "before_filter" entry. before_filters are code that gets executed before the action. The before_filter looks like this:

  before_filter :get_associated_blog

  protected
  def get_associated_blog
    if params[:nickname]
      @blog = Blog.find_by_nickname(params[:nickname])
    else
      @blog = nil
    end
  end

This finds the associated blog (if it can) and stores it as a member variable of the class. The action is then called, and the action can decide what to do if the blog hasn't been found which will typically be to redirect somewhere.

Modifying routing - recreate mapped routes; unnamed and named; use link_to with params to generate url! Needed to add another column: ruby script/generate migration add_usershortname_to_user user_id:string then migrate: rake db:migrate

  def show
    if @blog
      # work here
    else
      redirect_to(:controller => "blogs")
    end
  end

This is nice and easy. However you can also use the nickname to build URLs. For example when we create a new blog entry we want to re-direct back to the blog for this nickname. The code for that looks something like:

  def create
    
    if @blog       
        @blog_entry = BlogEntry.new(params[:blog_entry])

          if @blog_entry.save
            flash[:notice] = 'BlogEntry was successfully created.'
            format.html { redirect_to(:nickname => @blog.nickname) }

Notice that the redirect_to uses the nickname. This ensures that we redirect back to http://localhost/blog/kevin rather than http://localhost/blog.

Another place the nicknames are used are in the views. If we use the standard Rails mapping for routes then we can use the helper functions directly, something like:

<%= link_to 'Edit', edit_blog_path(@blog) %> |
<%= link_to 'Back', blogs_path %>

Where we use the default 'blogs' routing to build URLs for an edit entry and to get to the blogs controller's index page. Now that we've amended the blog_entry routes though we also need to track down and edit the uses of link_to (and url_for and any other use of these paths) in the view files. The primary change is to add the :nickname value to all the links. This is why we made @blog an instance variable in the BlogEntriesController class, so that it would be available in the views. The views will look something like:

<%= link_to 'Show', blog_entry_path(:nickname => blog_entry.blog.nickname, :id => blog_entry.id) %>
<%= link_to 'Edit', edit_blog_entry_path(:nickname => blog_entry.blog.nickname, :id => blog_entry.id) %>
<%= link_to 'Delete', blog_entry_path(:nickname => blog_entry.blog.nickname, 
                        :id => blog_entry.id), :confirm => 'Are you sure?', :method => :delete  %>
<%= link_to 'New Entry', new_blog_entry_path(:nickname => @blog.nickname) %>

Notice that for each of these routes we set the :nickname and the :id values. In fact this was the trickiest part of the entire procedure, figuring out what parameters I needed to pass to the link_to methods. For example there are times when I have to explicitly set :id to an empty string and others where the :id isn't needed at all.

There are some other changes I had to make along the way, notably:

  • Added foreign key constraints to blog table.
  • Change admin interface to add name
  • Change/add a create blog UI

Now that's done I intend to go back and fix up the 'blogs' routing so that it also supports nicknames.

Posted 10/11/2008

Rendering and Layouts

I'm assuming that most people reading this are .Net folk and that you will have at least a passing familiarity with ASP.Net, so occasionally I'll draw comparisons with Microsoft's framework. ASP.Net has had, since 2.0, the concept of Master pages. These are pages that provode the overall structure of parts of a web site. ASP.Net lets a page author design a template that contains content placeholders, this template is called a 'master page', individual pages can then specify that they want to use a specific master page and also specify which content replaces which placeholder within the page. Internally this turns out to be a fairly convoluted mechanism as the master page and the content page have to be woven together so that the right events get delivered to the right thing at the right time.

Rails, as you would expect, has a similar mechanism which known as 'layouts'.

In a Rails app, content is defined in a template. If you use the default generators of Rails 2.0 these templates are in the form of .erb files. A .erb file is essentially an HTML page with "turd-lets" of code embedded into it between <% %> and <%= %> symbols. This is just like other templating technologies such as ASP, ASP.Net, JSP or PHP

Templates are part of the view (obviously) and a typical template renders content for an action. For example in the rblog application there is a BlogsController class this has an index action with an associated index template. Of course none of this is mandatory, this is though the default behaviour if you use the scaffolding. The templates live in app/views and this index template lives in app/views/blogs/index.html.erb

The template looks something like this:

<h1>Latest Output</h1>


<div id='blog-entries-main-body'>
        <div id='blog-entries'>
                <% for blog_entry in @blog_entries %>
                        <div class='blog-entry-surround'>
                            <span class="blog-title"><%=h blog_entry.title %></span>
                            <div><%=h blog_entry.entry %></div>
                            <span class="blog-author"><%=h blog_entry.author_id %></span>
                        </div>
                <% end %>
        </div>
</div>

The template is a mixture of HTML and ruby. This Ruby code iterates over a collection of blog_entries (stored in @blog_entries) and for each one formats some output.

The action that causes this particular view to be rendered looks like:

def index
    @blog_entries = BlogEntry.find(:all, :order => 'updated_at DESC', :limit => 10)
    @blogs = Blog.find(:all)

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @blogs }
    end
  end

The first thing to note is that there are two member variables initialized, @blogs and @blog_entries. These are initialized in the controller and are also available in the view, that's where the view gets its @blog_entries reference from.

The other piece of interest is the respond_to section. This says that if the format is html use the default rendering (the '#' is a comment, so the 'index.html.erb' on that line is there as an aide memoire), and if the format is XML then use the XML renderer.

This means that when a request comes into /blogs then the index action of the blogs controller is executed, the @blogs and @blog_entries objects are created, control passes to the view and the output is rendered. However the output looks like this...

200810170814.jpg

There is some extra stuff in here, for example there is styling and also things like a Register and Login button. This extra HTML comes from the layouts.

The layouts live in the app/layouts directory and by default there is one layout per controller, this is generated as part of the scaffolding. This means that if nothing explicit is done a default layout will be used, however there are various other ways to specify the layout needed, as will be seen.

The layout used for the blogs page looks like this

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>RBlog: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'general' %>
</head>
<body>
<div class="header" id="header">
        <div id="header-message">
                Welcome to RBlog
        </div>
        <div id="flash">
                <p><%= flash[:notice] %></p>
        </div>
        <div id="logon">
                <%= link_to "Register", :action => "register", :controller => "authenticate"  %> 
                <%= link_to "Login", :action => "login", :controller => "authenticate" %>
        </div>
</div>

<div class="main" id="main">
<%= yield  %>
</div>
<div class="sidebar" id="sidebar">
        <div id="bloglist">
                <div id="blogs-list-title">Blogs</div>
                <% for blog in @blogs %>
                    <div class="blog-title"><%= link_to h(blog.title), blog %></div>
                <% end %>
        </div>
</div>

<div class="footer" id="footer">
        
</div>

</body>
</html>

The highlights are:

  • The use of <%= controller.action_name %> to get the name of the action used to show this page
  • <%= stylesheet_link_tag 'general' %> to load a stylesheet, more than one can be specified
  • The use if 'flash' to display messages
  • The use of link_to to display URLs
  • Access to data members from the controller (for blog in @blogs)
  • The call to yield


It's the last one that's most interesting here. It's the call to yield that says 'take the default rendered output from the view and display it at this point in the page'. One thing to note here is that the view 'code' has already been executed at this point, i.e. the view template is rendered, that output is saved, then the layout is executed and the output from the template inserted into the layout.

This layout is called 'blogslisting.html.erb' so is not the default layout for the blogs controller, this means that the code has to explicitly specify this layout to use. Like many things in Rails there is a great deal of flexibility in doing this.

A global or default layout can be specified in app/controllers/application.rb , this contains the base class that all controllers derive from. A 'layout' declaration can be added In the class definition.

class ApplicationController < ActionController::Base
  layout "general"

A layout can also be specified per controller

class BlogsController < ApplicationController

  layout "blogslisting"

And this can be further overwritten on a per action basis. So if a given controller wants different layouts for each action it can have code something like this:

  def show
    
    @blog = Blog.find(params[:id])

    respond_to do |format|
      format.html {render(:layout => "layouts/blog" )}
      format.xml  { render :xml => @blog }
    end
  end

Where the code in the format.html block says to render with the layout with the 'layouts/blogs' layout file.

This piece is already much longer than I though it would be so it's time to stop. There's more to be said about this, such as getting multiple content into the layout (multiple calls to yield), and sharing content with partial page templates. More on that soon.

Posted 22/10/2008

Rails - Database Access

One of the driving forces of Rails is to make things easier for developers. It does this partly by taking decisions out of developers hands. It's an 'opinionated' framework, and one of the opinions it has is on the pattern to use for database access. Its choice in this case is the 'active record' pattern.

Rails has an ActiveRecord module and the model classes all derive from

ActiveRecord::Base
for example
BlogEntry < ActiveRecord::Base

it is this module that provides the active record support for the framework.

Like much of the rest of Rails, ActiveRecord follows naming conventions. Here for example the BlogEntry class represents a row in the blog_entries table. How does this happen?

Looking back to the Learning Rails - Part 2 post you will see that this script was run

script/generate scaffold blog_entry ...

This script created two files, the file with the model class BlogEntry definition and a "migration". The migrations are "scripts" that help create and mange the database definitions, essentially they are DDL for Rails.

Migrations are used to both set up and tear down databases. The files contain class definitions that specify the steps to take when managing the database.

Migrations are timestamped so that it is easy to apply migrations in the correct order and to rollback those migrations in reverse order if needs be. The migrations live in the db/migrate directory. Currently there are 4 migrations in there

20080925064318_create_sessions.rb       
20080925065056_create_blogs.rb
20080925064319_create_users.rb          
20080925065210_create_blog_entries.rb

The first is a fairly standard Rails migration that creates the session tables (run rake db:sessions:create to create this), the others are specific to this application. Each migration has a date-time as part of the file name and it's this name that determines the order in which the migrations are run. The 20080925065210_create_blog_entries.rb looks like this

class CreateBlogEntries < ActiveRecord::Migration
  def self.up
    create_table  :blog_entries do |t|
      t.string    :title,             :null => false
      t.text      :entry,             :null => false
      t.integer   :author_id,         :null => false
      t.datetime  :entry_added_date
      t.datetime  :entry_last_edited
      t.timestamps
    end
  end

  def self.down
    drop_table :blog_entries
  end
end

So it's a class that derives from ActiveRecord::Migration and provides two class methods (static methods to C#/C++ folks), up and down (it's the "self" that indicates that these are class methods and not instance methods). You can run the migration from the command line by using the Rake command

rake db:migrate

This runs any migrations that have not yet been run. How does it know which migrations to run? Tthere is a database table that holds the information about the migrations that have been run.

$ mysql -u root
mysql> use rblog_development
mysql> show tables;

shows something like

+-----------------------------+
| Tables_in_rblog_development |
+-----------------------------+
| blog_entries                |
| blogs                       |
| schema_migrations           |
| sessions                    |
| users                       |
+-----------------------------+

and

mysql> select * from schema_migrations;


+----------------+
| version        |
+----------------+
| 20080923152418 |
| 20080923152427 |
| 20080923152435 |
| 20080925064318 |
| 20080925064319 |
| 20080925065056 |
| 20080925065210 |
+----------------+

on my machine as I type this. Notice that the last entry in the table matches the datetime portion of the name of the last migration file.

When a migration is run (assuming it has not yet been added to the database), then the self.up method is executed. This method creates or modifies database entries. In the case of the blog_entries migration it creates the table and adds the eight columns from these five entries.

(title, entry, author_id, entry_added_date, entry_last_edited and timestamps

mysql shows this

mysql> show create table blog_entries;
+--------------+---------------------------------+
| Table        | Create Table                    |
+--------------+---------------------------------+
| blog_entries | CREATE TABLE `blog_entries` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) NOT NULL,
`entry` text NOT NULL,
`author_id` int(11) NOT NULL,
`entry_added_date` datetime default NULL,
`entry_last_edited` datetime default NULL,
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1           |
+--------------+---------------------------------+

Notice that timestamps turns into two columns, and that an id column has been added as a primary key

A migration can also be rolled back. Running rake db:migrate rollback will rollback the last migration, or a specific version can be specified. For example rake db:migrate VERSION=20080925065056

Running the migrations this way runs the migrations in reverse order up to the specified migration, on the way the self.down mwthod of each migration is called. For the blog_entries migration that would drop the table. The down method should undo whatever the up method did!

One of the interesting (and frustrating) things about Rails is the way the migrations and the models work together. Running the script/generate scaffold blog_entry creates two files, the migration and the model. Looking in the model file there is ... nothing, just the class definition. The knowledge about the members of this class is in the migrations. This takes DRY (Do Not Repeat Yourself) to the limit but it can mean looking in several files (there maybe more than one migration per model) to find everything that the class uses. If the migrations get too "spread out", i.e. there are three or more migrations with modifications to one table then it is worth amalgamating those migrations into one file.

Posted 10/10/2008