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.
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
- Step3: Create ActiveRecord Models
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


