Technical
Blog On Rails In Five Or So

Part I ( mere introduction )

  1. Introduction {As stated by RubyOnRails.com }
    1. What’s in the package?
    2. Who is already on Rails?
  2. Create Rails App’s Structure
  3. Generate Controller and needed actions
    1. Controller Rules
  4. Database Creation and conventionalization
    1. Edit config/database.yml
    2. Model Rules
    3. Generate Models
    4. Create database{s}
  5. Scaffolding
    1. Manipulate schema using console
    2. Generate local scaffold

Part II (seriousnes on Rails )

  1. Scaffolding and why not to? – Lets ‘Feature UP’
  2. Posts
    1. List Post action
    2. Create Post action
      1. Error checking
    3. Show Post action
  3. Comments
    1. ‘Relate’ comments to posts
    2. Show Post’s comments
    3. AJAXify comments

Download zip pdf

Introduction {As stated by RubyOnRails.com }


What’s in the package?

Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern. From the Ajax in the view, to the request and response in the controller, to the domain model wrapping the database, Rails gives you a pure-Ruby development environment. To go live, all you need to add is a database and a web server.

Who is already on Rails?

Everyone from startups to non-profits to enterprise organizations are using Rails. Rails is all about infrastructure so it’s a great fit for practically any type of web application Be it software for collaboration, community, e-commerce, content management, statistics, management, you name it.

Create Rails App’s Structure.


 
[GregBluvshteyn@localhost RailsProjects]$ rails  BlogIn5
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts
      create  config/environments
      create  components
      create  db
      create  doc
      create  lib
      create  lib/tasks
      create  log
      create  public/images
      create  public/javascripts
      create  public/stylesheets
      create  script/performance
      create  script/process
      ...
Start The Server

 
localhost BlogIn5]$ cd BlogIn5/
localhost BlogIn5]$ ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2006-08-27 00:06:34] INFO  WEBrick 1.3.1
[2006-08-27 00:06:34] INFO  ruby 1.8.4 (2005-12-24) [i686-linux]
[2006-08-27 00:06:34] INFO  WEBrick::HTTPServer#start: pid=2729 port=3000

DefaultSite.png?

Create controller and needed actions(views)


Controller

Lets generate our Blog controller, for now we leave it empty.


BlogIn5]$ ruby script/generate controller Blog 
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/blog
      exists  test/functional/
      create  app/controllers/blog_controller.rb
      create  test/functional/blog_controller_test.rb
      create  app/helpers/blog_helper.rb

Now if you’d open \http://localhost:3000/blog/ you would see the following
blank_blog.png?


Database Creation and conventionalization

Edit config/database.yml

database.yml contains databse connection parameters written in YAML (aint YAML Ain’t Markup Language) which is a straightforward machine parsable data serialization format.

We will run our app on sqlite ( the most agile db engine out there).
Sustitute automatically generated text of config/database.yml with:


development:
  adapter: sqlite3
  database: db/BlogIn5_development.sqlite3

test:
  adapter: sqlite3
  database: db/BlogIn5_test.sqlite3

production:
  development

Model Rulles

  1. Model names (table names) are plural.
    For example:
    people, fish, sheep, tools e.t.c
  2. Primary key named with id, foreign named #{parent_name}_id
    For example:
    Parent table posts would have id as a primary key, and a child table comments would have a foreign key as post_id.
  3. Should be genearted using singular form which rails will create a table using plural form.
    For example:
    people table would be generated using
    
    ruby script/generate model Person
    # will create people table, or
    ruby script/generate model Blog
    #will create blogs table
    

Generate Models

We will generate model Post for the posts table, it will be enough for the beggining.


cd BlogIn5
@localhost  BlogIn5]$ ruby script/generate model Post 
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/post.rb
      create  test/unit/post_test.rb
      create  test/fixtures/posts.yml
      create  db/migrate
      create  db/migrate/001_create_posts.rb

Following commands generated following file:


#app/models/post.rb
class Post < ActiveRecord::Base
end

Create database(s)

By using rake ( ruby’s make but with much simplar syntax) we will execute command that will create our database with, for now, empty schema.


localhost BlogIn5]$ rake migrate
(in /home/monozub/RailsProjects/BlogIn5)
 CreatePosts: migrating ===================================================
—create_table(:posts) → 0.0251s
CreatePosts: migrated (0.0259s) ==========================================

By checking your db folder( that contains all the database objects ( stuff related to database) you will see that BlogIn5_development.sqlite3 database was created. Also, by usign simple sqlite client we can check if database is functional.

localhost BlogIn5]$ sqlite3 db/BlogIn5_development.sqlite3
SQLite version 3.3.3
Enter ".help" for instructions
sqlite> .scehma
unknown command or invalid arguments:  "scehma". Enter ".help" for help
sqlite> .schema
CREATE TABLE comments ("id" INTEGER PRIMARY KEY NOT NULL);
CREATE TABLE posts ("id" INTEGER PRIMARY KEY NOT NULL);
CREATE TABLE schema_info (version integer);
sqlite>.quit

Also you will find db/schema.rb file that contains your schema DDL using ruby’s migration syntax. Currently it will be empty since we did not added any fields yet.

Scaffolding

Lets edit our Blog controller.


class BlogController < ApplicationController
end
#Replace with 
class BlogController < ApplicationController
 scaffold :post
end
Reload server by pressing Ctrl+C since it has to be done every time there are any changes in the schema.

Lets generate another migration and add some fields to our posts table.


ocalhost BlogIn5]$ ruby script/generate migration AddTitleToPost
      exists  db/migrate
      create  db/migrate/003_add_title_to_post.rb

Edit db/migrate/003_add_title_to_post.rb to look as following

class AddTitleToPost < ActiveRecord::Migration
  def self.up
    add_column :posts,:title, :string
    Post.create :title => "This is a first post" #This line will be useful for, and explained later.
  end

  def self.down
  end
end
Regenerate your database using rake migrate

localhost BlogIn5]$ rake migrate
(in /home/monozub/RailsProjects/BlogIn5)
 AddTitleToPost: migrating ================================================
—add_column(:posts, :title, :string) → 0.0521s
AddTitleToPost: migrated (0.0531s) =======================================
Reload webserver and refresh http://localhost:3000/blog

WOW

blog_with_title.pmg?

We have all the CRUD functionality for the table. Magick huh?. And this is just a begining.

Lets add some more fields by using rails interactive console.


localhost BlogIn5]$ ruby script/console
Loading development environment.
>> ActiveRecord::Schema.define do
?> add_column :posts, :body,:text
>> end
-- add_column(:posts, :body, :string)
   -> 0.0514s
=> nil
>>

As you can ses I add another field body to the posts table.
Lets check how our app looks after restarting it.
blog_with_body_edit.png?

WOV, we got another field totally ready for us to use, and as you could see all I did was addition of another field. No HTML or code what so ever was needed to do that.
Just out of curiousity lets add a date field since, we would want to track when the post was created.


>> ActiveRecord::Schema.define do
?> add_column :posts, :created_at , :datetime
>> end
-- add_column(:posts, :created_at, :datetime)
   -> 0.1671s
=> nil

Now lets see what happens when we restart the server.
blog_with_created_at_field.png?

Generate local scaffold

Also the previous procidures generate scaffold in memorey. Lets generate the structure to be accessed locally.

  1. Change Blog controller to look as following
    
    class BlogController < ApplicationController
     #scaffold :post
    end
    
  2. Generate scaffolding for your post model
    
    localhost BlogIn5]$ ruby script/generate scaffold Post
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/posts
          exists  test/functional/
      dependency  model
          exists    app/models/
          exists    test/unit/
          exists    test/fixtures/
       identical    app/models/post.rb
       identical    test/unit/post_test.rb
       identical    test/fixtures/posts.yml
          create  app/views/posts/_form.rhtml
          create  app/views/posts/list.rhtml
          create  app/views/posts/show.rhtml
          create  app/views/posts/new.rhtml
          create  app/views/posts/edit.rhtml
          create  app/controllers/posts_controller.rb
          create  test/functional/posts_controller_test.rb
          create  app/helpers/posts_helper.rb
          create  app/views/layouts/posts.rhtml
          create  public/stylesheets/scaffold.css
    

This command generates all the necesary controller and view files in order to perform CRUD opperation on the Post table.

Part II ( seriousnes on Rails )

Scaffolding – why not?

  1. Scaffold does not recognizes database relationships.
  2. Scaffold generates lots of code that we may not need.
  3. Some times its easier to create from scratch then edit created code.

Sad that lets disable scaffolding.


class BlogController < ApplicationController
  #scaffold :post
end

Posts

Add some actions to our Blog controller such as: List, Show, New. We will skip delete function, since it should belong to administrative actions rather then public ones.


localhost BlogIn5]$ ruby script/generate controller Blog list show 
      exists  app/controllers/
      exists  app/helpers/
      exists  app/views/blog
      exists  test/functional/
overwrite app/controllers/blog_controller.rb? [Ynaq] Y
       force  app/controllers/blog_controller.rb
   identical  test/functional/blog_controller_test.rb
   identical  app/helpers/blog_helper.rb
      create  app/views/blog/list.rhtml
      create  app/views/blog/show.rhtml


Now that we have our actions, lets modify the controller to get our records.

List action

First, we want users to be fowarded to the list action when the navigate to the blog controller withouth specifying any actions. It can be accoplished adding the following to the index method.


  def index
    list
    render :action => 'list'
  end

Here, I’m executing list methods and then rendering ( calling template of) list action.
So now if you’d navigate to http://localhost:3000/blog you’d see the following.
blog_list_action.png?
Lets modify the list method in app/controllers/blog_controller.rb to look as following.

def list
  @posts = Post.find(:all) #Equal to SELECT * FROM posts
end

Now lets edit list template which is located in app/views/blog/list.rhtml to look as foolowing

<h1> Blog </h1>
<% for post in @posts %>
    <p><strong><%= post.title %></strong></p>
    <p><%= post.body %></p>
<% end %>

Wov – this is quick?
blog_without_form.png?
On line number 2 we used a for loop that was used for extranction post objects from the posts object collection
< > tags are used to show to the template that we are passing ruby code. If you’d want to output data you’d use <= > for execution tag is used without the equal sign.

Create new post

Add a form that will create a new post to the app/views/blog/list.rhtml


<h1> Blog </h1>

<% for post in @posts %>
    <p><strong><%= post.title %></strong></p>
    <p><%= post.body %></p>
<% end %>
<br>
<%= start_form_tag :action => "create" %>
<p><label for="post_title"><b>Title</b></label>
     <%= text_field 'post','title' %></p>
<p><label for="post_body"><b>Body</b></label><br/>
    <%= text_area 'post','body' %><br/>
  <%= submit_tag "Create" %><p style="color:red"><%= flash[:error] %></p>
<%= end_form_tag %>

Here you see lots of new tags such as start_form_tag, end_form_tag, text_field, text_area this are default form helpers that ship with rails by default.
Also, I added a flash[:error], which is a structure that lets us conviniently pass either error messages to the user from controller.

Now that we have form, lets edit our controller’s create method (action)


  def create
    @post = Post.new(params[:post])
    @post.created_at = Time.now
    if @post.save
      flash[:notice] = 'Post was successfully created'
      redirect_to :action => "list" 
    end
 end

So first we create a new post sending it params[:post] which is a hash of post object send by the form that would look as following

"post"=>{"body"=>"This is a body for the third post", "title"=>"Post # 3"}

Rails automatically unwraps it in to the object and loads the value of a hash for as the attributes of the post object. As you can see I also added manually created_at value since I’m not passing it using the form. When we execute @post.save it either returns true on success or false other wise, so I flash a notice of succesfull and redirect user back to the list action.

Hey, How about error checking!

Not a problem, Rails as always has a beautiful solution for us.
Lets add validation helper to our model file app/models/post.rb


class Post < ActiveRecord::Base
  validates_presence_of :title,:body
end

and an error messge for our list template

<%= error_messages_for 'post' %>
<%= start_form_tag :action => "create" %>...

finally, some small changes needed to be done to the controller.
Remember, that we did not do anything in case of failed save, now we will correct that mistake.

def create
  @post = Post.new(params[:post])
  if @post.save
    flash[:notice] = 'Post was successfully created'
    redirect_to :action => 'list'
  else
    @posts = Post.find(:all)
    render :action => 'list'
 end
end

Now, save files we will render action list rather then redarecting to it in order to fill the error_messages_for function with an array of errors passed by our validates_presence_of method.
blog_list_with_errors.png?

Show action

First lets make Title on the list action in to be a link to the show action.
We will be passing a post id for the post to be shown.

  1. Change line in the app/views/blog/list.rhtml to look as following
    
    #change
    <p><strong><%= post.title %></strong></p>
    #to 
    <p><strong><%= link_to  post.title,:action => "show",:id => post.id %></strong></p>
    
  2. Change app/views/blog/show.rhtml to look as following
    
      <p><strong><%= @post.title %></strong></p>
      <p><%= @post.body %></p>
      <p><b>Created @:</b><%= @post.created_at %>
    
  3. Finally lets change the controller to find neede post for us. We will update show method in app/controllers/blog_controller.rb to look as following
    
      def show
        @post = Post.find(params[:id])
      end
    

Comments

Lets generate some more actions.

  1. Add create_comment method to app/controllers/blog_controller.rb
  2. Add comments table and neccassary models.
    
    localhost BlogIn5]$ ruby script/generate model Comment
          exists  app/models/
          exists  test/unit/
          exists  test/fixtures/
          create  app/models/comment.rb
          create  test/unit/comment_test.rb
          create  test/fixtures/comments.yml
          exists  db/migrate
          create  db/migrate/003_create_comments.rb
    
  3. Edit db/migrate/003_create_comments.rb migration to add necessary fields.
    
    class CreateComments < ActiveRecord::Migration
      def self.up
        create_table :comments do |t|
          # t.column :name, :string
          t.column :name,:string
          t.column :email,:string
          t.column :site,:strig
          t.column :body,:text
          t.column :post_id,:integer
        end
      end
    
      def self.down
        drop_table :comments
      end
    end
    #Migrate database
    ocalhost BlogIn5]$ rake migrate
    (in /home/monozub/RailsProjects/BlogIn5)
     CreateComments: migrating ================================================
    —create_table(:comments) → 0.0289s
    CreateComments: migrated (0.0306s) =======================================

‘Relate’ comments to posts

Creating relations on Rails is the most beautiful thing to do so we will use methods such as has_many, belongs_to, has_one e.t.c Using our database methodology we can see of this methods comparing them to references key word.
For example one to many relation between posts and comments we would in the DDL of comments table we would use post_id references posts(id) ( depending on the DDL of particular db server). The same relation would be represented on Rails as following.
  1. Edit you app/models/comment.rb to look as following
    
    class Post < ActiveRecord::Base
      has_many :comments
      validates_presence_of :title,:body
    end
    
  2. Edit your app/models/post.rb to look as following
    
    class Comment < ActiveRecord::Base
      validates_presence_of :name,:email,:body
      belongs_to :post
    end
    
    

    To check how it works lets use our intractive ruby console again.
    
    localhost BlogIn5]$ ruby script/console
    Loading development environment.
    >> comment = Post.find(1).comments.create(:name => "Gregory Bluvshteyn")
    => #<Comment:0xb77fe5d4 @new_record=false, @errors=#<ActiveRecord::Errors:0xb77fa164 @errors={}, @base=#<Comment:0xb77fe5d4 ...>>, @attributes={"name"=>"Gregory Bluvshteyn", "body"=>nil, "post_id"=>1, "id"=>1, "site"=>nil, "email"=>nil}>
    >> comment.email = "greg.bluvshteyn _at_ gmail.com" 
    => "greg.bluvshteyn _at_ gmail.com" 
    >> comment.body = "I'd like to comment......" 
    => "I'd like to comment......" 
    >> comment.save
    => true
    #This is how the same looks in our logs using sql statments 
      Comment Load (0.000884)   SELECT * FROM comments WHERE (comments.post_id = 1)
      SQL (0.004419)   PRAGMA table_info(comments)
      SQL (0.001560)   INSERT INTO comments ("name", "body", "post_id", "site", "email") VALUES('Gregory Bluvshteyn', NULL, 1, NULL, NULL)
      Comment Update (0.001451)   UPDATE comments SET "email" = 'greg.bluvshteyn _at_ gmail.com', "name" = 'Gregory Bluvshteyn', "site" = NULL, "body" = 'I''d like to comment......', "post_id" = 1 WHERE id = 1
    #quit massy isnt it?
    

Show Post’s comments

  1. ‘Create comment’ Form
    
    <%= error_messages_for 'comment' %> <!-- Lets not forget the errors -->
    <%= start_form_tag :action => "create_comment", :id => @post.id %>
      <p><label for="comment_name"><b>Name:</b></label>
        <%= text_field 'comment','name' %></p>
      <p><label for="comment_email"><b>Email</b></label>
        <%= text_field 'comment','email' %></p>
      <p><label for="comment_site"><b>Site:</b></label>
        <%= text_field 'comment','site' %></p>
      <p><label for="comment_body"><b>Body</b></label>
        <%= text_area 'comment','body' %></p>
      <%= submit_tag "Create Comment" %>
    <%= end_form_tag %>
    
  2. List all the comments on the show action
    Since, we already have our relationships we don’t need any extra methods to get the comments separatly they will be added to the post object that we create with show action. So, just add the folowing above the create form.
    
    <h1>Comments</h1>
    <div id="comments_div">
      <% for comment in @post.comments %>
        <b>Commenter Name:</b><%= comment.name %><br>
        <b>Commenter Email:</b><%= comment.email %><br>
        <b>Commenter Site:</b><%= comment.site %>
        <b>Body</b><br>
        <%= comment.body %>
     <% end %>
    </div>
    
  3. Finally add the code to the create_comment action in the app/controllers/blog_controller.rb
    
      def create_comment
        @comment = Post.find(params[:id]).comments.create(params[:comment])
        if @comment.save
          flash[:notice] = 'Comment was added succesfully'
          redirect_to :action => 'show',:id => params[:id]
        else
          @post = Post.find(params[:id])
          render :action => "show",:id => params[:id]
        end
      end
    

AJAXify comments

Usage of ajax is another Rail’s beauty. Lets ajax our comment creation in couple of easy steps.

  1. Add prototpy libraries to the head of our layout.
    
    <%= javascript_include_tag :defaults %>
    
  2. Move comments listing to the partial template(app/vies/blog/_comments.rhtml).
  3. Change content of comments_div on the app/views/blog/show.rhtml to
    
    <div id="comments_div">
      <%= render :partial => "comments" %> 
    </div>
    
  4. Since error_messages_for function will not work with Ajax we going to need to write something similar to it. Add folowing to your partial comment to look as following.
    
    <% error_messages_for 'comment' %>
     <% if !@errors.nil? %>
      <% @errors.each do |error| %>
        <li style="color:red"><%= error %></li>
      <% end %>
     <% end %>
      <% for comment in @post.comments %>
        <b>Commenter Name:</b><%= comment.name %><br>
        <b>Commenter Email:</b><%= comment.email %><br>
        <b>Commenter Site:</b><%= comment.site %>
        <b>Body</b><br>
        <%= comment.body %>
      <hr>
     <% end %>
    
  5. Change form header to use ajax rather then plain post.
    
    #change from 
    <% start_form_tag :action => "create_comment", :id => @post.id %>
    #to
    <%= form_remote_tag(:update => "comments_div",
                        :url => {:action => "create_comment",:id => @post.id}) %>
    
  6. Finally lets add changes to our controller app/controller/blog_controller.rb
    
      def create_comment
        @comment = Post.find(params[:id]).comments.create(params[:comment])
        @post = Post.find(params[:id])
    
        if !@comment.save
          @errors = @comment.errors.full_messages
        end
        render :partial => "comments" 
      end
    
  7. Finally lets add the indicator(indicator.gif?)
    Add span next to your create comment form app/views/blog/show.rhtml
    
    ...
    <%= end_form_tag %>
    <span id="indicator" style="display:none"><%= image_tag "indicator.gif" %></span>
    
  8. Change form_remote_tag to look as following in app/views/blog/show.rhtml
    
    <%= form_remote_tag(:update => "comments_div",
                        :before => "Element.show('indicator')",
                        :complete => "Element.hide('indicator')",
                        :url => {:action => "create_comment",:id => @post.id}) %>