Web 2.0 powered Blog in 15 min - RoR Magic

From IlugCal

[edit] Riding The Rails

Whats up with Rails? Why isn't my old dog good enough?
Ruby on Rails is a Web Framework that is optimized for programmer happiness and sustainable productivity. It lets you write beautiful code following convention over configuration.

Design Overview
Rails is designed based on MVC design patterns.

Image:rails-mvc.jpg


Before proceeding, it is expected the readers are familiar with very basic HTML, Web 2.0 and RDBMS and has the following pre-requirements handy:

* Ruby 1.8.x (Tested with Ruby 1.8.5)
* Rails 1.2.x (Tested with Rails 1.2.3)
* MySQL/PostgreSQL/SQL-Lite/MSSQL/Oracle (Tested with PostgreSQL 8.0.9)
* Internet to browse http://api.rubyonrails.org
  • Step1: Create the rails project
    neo@sauron ~/ROOT/personal/ilug-cal $ rails ./blog
  • Step2: Generate the SQL schema and create database
    CREATE TABLE users (
       user_id SERIAL NOT NULL,
       name VARCHAR(128) NOT NULL,
       PRIMARY KEY (user_id)
    );
    CREATE TABLE posts (
       post_id SERIAL NOT NULL,
       subject VARCHAR(128) NOT NULL,
       body TEXT NOT NULL,
       user_id INT4,
       PRIMARY KEY (post_id)
    );
    ALTER TABLE posts ADD FOREIGN KEY (user_id) REFERENCES users (user_id);
    CREATE UNIQUE INDEX IDX_users1 ON users (user_id);
    CREATE INDEX IDX_posts1 ON posts (user_id);

    neo@sauron ~/ROOT/personal/ilug-cal/blog $ psql -d blog -U abhisek < ./db/create.sql
    CREATE TABLE
    CREATE TABLE
    ALTER TABLE
    CREATE INDEX
    CREATE INDEX

Our simple database consists of two table, users and posts with a user contains many posts type of relationship ie. users -- One to Many --> posts. You also need to tell rails about your database configuration. Rails accepts database configuration from ./config/database.yml file in the project root.
My database.yml file looks like this:

    development:
      adapter: postgresql
      database: blog
      username: abhisek
      password: XXXXXX
      host: localhost
    test:
      development

ActiveRecord provides database abstraction for application developers. It is called The Object-Relatioship Mapping or The ORM layer. For each table we will create a model in rails and we will access the table and all its columns as any other objects in Ruby.

     neo@sauron ~/ROOT/personal/ilug-cal/blog $ ./script/generate model user
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/user.rb
      create  test/unit/user_test.rb
      create  test/fixtures/user.yml
      create  db/migrate
      create  db/migrate/001_create_user.rb

     neo@sauron ~/ROOT/personal/ilug-cal/blog $ ./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/post.yml
      exists  db/migrate
      create  db/migrate/002_create_post.rb

The users model looks like this:

     
      class User < ActiveRecord::Base
         set_primary_key "user_id"
         has_many :posts
      end

The posts model looks like this:

      class Post < ActiveRecord::Base
         set_primary_key "post_id"
         belongs_to :user
      end
  • Step4: Getting the database online - Scaffolding

We don't want to write a user registration, login or such stuff for our demo. We just want something to quickly add users which we will use during blogging. All fancy stuff will be done in blogging page.

So lets get the database online.

      neo@sauron ~/ROOT/personal/ilug-cal/blog $ ./script/generate scaffold user
      exists  app/controllers/
      exists  app/helpers/
      exists  app/views/users
      exists  app/views/layouts/
      exists  test/functional/
      dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      .
      . + Output stripped +
      .
      create  app/views/layouts/users.rhtml
      create  public/stylesheets/scaffold.css

So, basically we generated a scaffold of name "user". Rails will automagically use users model as the underlying table and by enumerating the table attributes it will generate HTML views for possible input output from the users table in the database.

Start the application server for checking the results:

      neo@sauron ~/ROOT/personal/ilug-cal/blog $ ./script/server
      => Booting Mongrel (use 'script/server webrick' to force WEBrick)
      => Rails application starting on http://0.0.0.0:3000
      => Call with -d to detach
      => Ctrl-C to shutdown server
      ** Starting Mongrel listening at 0.0.0.0:3000
      ** Starting Rails with development environment...
      ** Rails loaded.
      ** Loading any Rails specific GemPlugins
      ** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
      ** Rails signals registered.  HUP => reload (without restart).  It might not work well.
      ** Mongrel available at 0.0.0.0:3000
      ** Use CTRL-C to stop.

Now check: http://127.0.0.1:3000/users/ If everything goes right, you should get a naked page for doing I/O with your users table. Add some dummy users and proceed..

  • Step5: The Blog Controller

Generate the blog controller

      neo@sauron ~/ROOT/personal/ilug-cal/blog $ ./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

We will have three basic actions in our controller:
1) post - To create a new blog post
2) list - List all blog posts
3) view - View specified blog post

The post action contains:

  def post
    if params[:subject].nil? or params[:body].nil? or params[:user_name].nil?
      @users = User.find_all
    else
      u = User.find_by_name(params[:user_name])

      p = Post.new
      p.subject = params[:subject]
      p.body = params[:body]

      u.posts.push p
      p.save!
    end
  end

Once a action in the controller is executed, its corresponding view is rendered to display the output on the browser. An action can either use the "render" method to explicitly tell rails which view to render, else rails by default renders action_name.rhtml view, ie. in case of action "post", the "post.rhtml" view is rendered.

  • Step6: The View

Just like Ruby itself, the RoR views are also object oriented in its true essence. We will be considering each part of the view to render on browser as an object.

To make our task easier, we will define a global layout for our blog application and for each view we will just write the minimal code that is required, following both KISS (Keep it short and Simple) and DRY (Don't Repeat Yourself) principle.

The global layout is defined by ./app/views/layouts/application.rhtml file in your project root. In my case:

<html>
  <head>
    <title><%= controller.action_name %></title>
    <%= javascript_include_tag :defaults %>
  </head>

  <body>

    <%= yield %>

  </body>
</html>

Now its time to write a view (post.rhtml) for our action post. This action is intelligent enough to serve two purpose. Both as serving the initial Blog Posting form as well as saving the Blog Post automatically.

Out post.rhtml contains the following:

<div id='post' class='postbox'>
  <% form_remote_tag :update => 'postres', :url => {:action => 'post'} do -%>
  <table>
    <tr>
      <td>Name:</td>
      <td>
        <% opt = @users.collect {|u| "<option>#{u.name}</option>" } %>
        <%= select_tag "user_name", opt %>
      </td>
    </tr>
    <tr>
      <td>Subject:</td>
      <td>
        <%= text_field_tag "subject", nil, :maxlength => 32 %>
      </td>
    </tr>
    <tr>
      <td>Body:</td>
      <td>
        <%= text_area_tag "body" %>
      </td>
    </tr>
    <tr>
      <td><%= submit_tag "Create" %></td>
      <td>
        <div id='postres' class='poststatus'>
          <i>Click on create to post</i>
        </div>
      </td>
    </tr>
  </table>
  <% end %>
</div>

Do you see the form tag in the view code?

<% form_remote_tag :update => 'postres', :url => {:action => 'post'} do -%>

It will be rendered in the browser as follows:

<form action="/blog/post" method="post" onsubmit="new Ajax.Updater('postres', '/blog/post', {asynchronous:true, 
evalScripts:true, parameters:Form.serialize(this)}); return false;">

Follow http://127.0.0.1:3000/blog/post to play around and add few posts before we proceed to viewing and listing.

Now lets take a look at the view that is used for listing and viewing posts dynamically.

<div id='postlist' class='listbox'>
<table border=2>
  <tr>
    <td>Author</td>
    <td>Subject</td>
    <td>Action</td>
  </tr>
  <% @posts.each do |post| %>
    <tr>
      <td><%= post.user.name %></td>
      <td><%= post.subject %></td>
      <td>
        <%= link_to_remote "View", :update => 'postview', :url => {:action => 'view', :id => post.post_id} %>
      </td>
    </tr>
  <% end %>
</table>
</div>

<div id='postview' class='viewbox'>
  <!-- AjaxUpdate will render Post data here -->
</div>

Take a look at the dynamic part:

<%= link_to_remote "View", :update => 'postview', :url => {:action => 'view', :id => post.post_id} %>

This code will render a AjaxUpdate in the browser as follows:

<a href="#" onclick="new Ajax.Updater('postview', '/blog/view/1', {asynchronous:true, evalScripts:true}); 
return false;">View</a>
  • Step7: Partial Rendering

Partial rendering refers to rendering an object/part of the view dynamically using Ajax. The web browser Ajax stack fetches data from the server and updates the specified component of the view in the browser dynamically.

Lets take a look at our action responsible for rendering a partial view:

  def view
    @post = Post.find(params[:id].to_i)

    render :partial => 'view'
  end

Previously in our list view, we have seen using link_to_remote() method we can define a AjaxUpdater object using which dynamic fetching is possible. In our list view example, the list action in blog controller is invoked which renders a partial view of name 'view'.

Rails, following convention over configuration principle, uses _view.rhtml in ./app/views/blog directory for rendering the partial view of name 'view'.

Our _view.rhtml contains the following:

<table>
  <tr>
    <td>Author</td>
    <td><%= @post.user.name %></td>
  </tr>
  <tr>
    <td>Subject</td>
    <td><%= @post.subject %></td>
  </tr>
  <tr>
    <td>Body</td>
    <td><%= @post.body %></td>
  </tr>
</table>

[edit] Screen Cast

Finally, Click Here to download the screencast of the resulting application.

[edit] References

* Ruby
* Ruby on Rails
* RoR API Reference
* Understanding ActiveRecords
* Rails Cheat Sheet


Source

Personal tools