Wednesday, 22 October 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 by kevin at 7:07 AM in Ruby

Friday, 10 October 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 by kevin at 3:37 PM in Ruby

« October »
SunMonTueWedThuFriSat
   1234
567891011
12131415161718
19202122232425
262728293031