Recaping where my first Rails post left off, we had a functioning albeit boring application. In this post we’ll expand the applicaton to include the comment table.

More code generation

In the previous post we used Rails to generate a portion of our application:

$ ruby script/generate model Post$ ruby script/generate controller posts

Then used “scaffold :post” in our controller to tell Rails to dynamicly generate CRUD code for us. Now, we need to generate the same scaffold statically instead so that it may be customized. In the /Blog directory:

$ ruby script/generate scaffold Post$ ruby script/generate scaffold Comment

Start up the web server and have a look.

$ ruby script/server

You can access the newly created code from http://localhost:3000/posts and http://localhost:3000/comments. Note that the links have changes from the singular to the plural(need to find out the logic as to why it does this).


Our Blog application uses a simple one to many relation(one post has many comments). That relation is defined in our data model.Edit app/models/comment.rb:

class Comment < ActiveRecord::Base    belongs_to :post    validates_associated :postend

and in app/models/post.rb

class Post < ActiveRecord::Base    has_many :commentsend

Now we need to tell the view and the controller about the relation. Edit /controllers/posts_controller.rb:

def show    @post = Post.find(@params[:id])    @comments = @post.find_all_in_commentsend

Next we edit the view to include the comments and a link to add a new comment. Edit app/views/posts/show.rhtml adding:

<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= link_to 'Add Comment', :controller => 'comments', :action => 'new',     :post_id => @post['id'] {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}> |


<br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} for comment in @comments {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}=h comment.title {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}=h comment.body {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} end {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}>

So that the file now looks like:

<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} for column in Post.content_columns {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><p><b><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= column.human_name {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}>:</b> <{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}=h @post.send( {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}></p><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} end {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= link_to 'Add Comment', :controller => 'comments',     :action => 'new', :post_id => @post['id'] {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}> |<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= link_to 'Edit', :action => 'edit', :id => @post {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}> |<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= link_to 'Back', :action => 'list' {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} for comment in @comments {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}=h comment.title {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}=h comment.body {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><br /><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104} end {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}>

The post id is passed to the comment form in the url parameters. We need to tell the comments controller to grab the post id and include it in the "new" form. Edit app/controller/comments_controller.rb adding:

@comment.post_id = @params['post_id']

so the new method looks like:

def new    @comment =    @comment.post_id = @params['post_id']end

By default the comment create method(what the new method calls) redirects back to the comments listing. We want it to redirect to the posts listing. Edit the create method so that it looks like:

def create    @comment =[:comment])    if        flash['notice'] = 'Comment was successfully created.'        redirect_to :controller => 'posts', :action => 'list'    else        render_action 'new'    endend

":controller => 'posts' tells the method to use the posts controller instead of the default comments.
We also need to edit the comments view to include to post id that we pass it. While we are at it, lets change the title form field to a text field instead of a text area. Edit app/views/comments/_form.rhtml adding:

<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= hidden_field 'comment', 'post_id'  {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}>

and changing:

<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= text_area 'comment', 'title'  {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}></p>

so the the content is:

<{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= error_messages_for 'comment' {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><p><label for="comment_title">Title</label><br/><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= text_field 'comment', 'title'  {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}></p><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= hidden_field 'comment', 'post_id'  {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}><p><label for="comment_body">Body</label><br/><{9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}= text_area 'comment', 'body'  {9a59223fbbfa3706fcb6f14a9d1c0e9d874b8faac7a72cc56b28c77d373fa104}></p><p><label for="comment_created_on">Created on</label><br/></p><p><label for="comment_updated_on">Updated on</label><br/></p>

Add a new post by visiting http://localhost:3000/posts/new, then click on the "Show" link of the posts listing. You should now see a "Add Comment" link. Add some comments, then view the post again. You should see the comment(s) you posted attached to the post.
More to come.


Since Rails is now in Debian Unstable(sid), not much to say about installation. It’s as easy as:

apt-get install rails


The blog application needs two tables: posts, comments. Following the Rails recommended table and column naming conventions is a good idea and will save even more time later. Every table should be named in the plural, have a primary key called “id”, set as auto increment. Links to other tables should be in the form “singualOfForeignTableName_id”. Rails will automatically maintain fields called created_at/created_on and/or updated_at/updated_on(timestamp). A field called lock_version(integer default of 0) should also be included to allow Rails to do optimistic locking in a multi-user system.

Creating the Database and Tables

create database Blog;CREATE TABLE `posts` (    `id` smallint(5) unsigned NOT NULL auto_increment,    `title` text NULL default '',    `body` text NULL default '',    `created_on` timestamp(14) NOT NULL,    `updated_on` timestamp(14) NOT NULL,    PRIMARY KEY (`id`)    ) TYPE=MyISAM COMMENT='List of posts';CREATE TABLE `comments` (    `id` smallint(5) unsigned NOT NULL auto_increment,    `title` text NULL default '',    `body` text NULL default '',    `post_id` smallint(5) unsigned NOT NULL,    `created_on` timestamp(14) NOT NULL,    `updated_on` timestamp(14) NOT NULL,    PRIMARY KEY (`id`),    INDEX `post_id` (`post_id`)    ) TYPE=MyISAM COMMENT='List of comments';

Rails Setup

Rails generates a good bit of code for you. Now that we have our database/tables in place we can run the Rails script to create our initial Blog application shell. The following command will create a Blog subdirectoryand application shell.

$ rails Blog

Now we need to tell Rails how to connect to our database by editing the database.yml file.

$ cd Blog$ vi config/database.yml

The file will look something list this:

development:    adapter: mysql    database: rails_development    host: localhost    username: root    password: test:    adapter: mysql    database: rails_test    host: localhost    username: root    password:production:    adapter: mysql    database: rails_production    host: localhost    username: root    password: 

You will need to edit the development section to something like this:

development:    adapter: mysql    database: Blog    host: localhost    username: root    password:

change your database/host/username/password as appropriate.Next we will run a few more code generation script to produce a model and controller shell for our posts table.

$ ruby script/generate model Post$ ruby script/generate controller post

The model name must start with a capital letter and should be the singular name of the related table.Now we need to link our controller to the model. Edit the post_controller.rb that was generated for you:

$ vi app/controllers/post_controller.rb

Adding model:

class PostController < ApplicationController    model :postend

We are now ready to test the application. Rails will run under Apache, but for this example we will use the build in WEBrick http server. In the /Blog directory:

$ ruby script/server

You should now be able to connect to your application from http://localhost:3000 and then access the application http://localhost:3000/post.

Unknown actionNo action responded to index

Not very nice, but it's working. Lets make it nicer by allowing Rails to fill in the details.Edit post_controller.rb again and add "scaffold :post".

class PostController < ApplicationController    model :post    scaffold :postend

Now access http://localhost:3000/post/

Listing postsTitle   Body    Created on  Updated onNew post

You've got a functioning application. More to come.

As of late I’ve begun to tire of writing the same old CRUD interfaces over and over. So I’ve been looking at web app. frameworks that may remove some of the drudgery. The contenders are:

Currently, Maypole is my top contender. It’s Perl based(we do lots of Perl), seems to do “enough” without doing too much and end up being limiting or just get in the way. CherryPy looks to be a Pythonic clone of Rails, which in itself isn’t a bad thing, but it looks even younger than Rails and doesn’t seem to have the same momentum. So this leaves Rails. I’ve done some toy coding with Ruby in the past and liked it.

Ruby is a fully OO language with a concise “perlish” syntax and Rails is an MVC framework on top of it. As a Rail learning/evaluation attempt, I plan it to try and implement a simple blog and document the progress as posts here. Depending on how the Rails attempt goes, I’ll reimplemented the same thing in CherryPy and Maypole.

Stay tuned. This should be fun.

Came across a great new shell tip. Brace expansion.

Some examples:

 $echo {one,two,three,four}" fish"one fish two fish three fish four fish$ echo {{1,2,3}1,2,3}11 21 31 2 3

Ok, that’s great. What’s it good for:

$ cp /tmp/test.html /tmp/test.html_bak

Can be rewritten as:

cp /tmp/test.html{,_bak}

Then after you makes some changes to /tmp/test.html:

$ diff /tmp/test.html{_bak,}

Works in bash, zsh and possibly others.

Amazingly, this fellow visited MUSC last week. Had the opportunity to chat with him about open source, Linux, IBM and Jabber. Refreshing to meet someone that really does “get it”.