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.
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.
[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
...
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?
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.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
ruby script/generate model Person
# will create people table, or
ruby script/generate model Blog
#will create blogs table
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
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) ==========================================
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
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
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
blog_with_title.pmg?
We have all the CRUD functionality for the table. Magick huh?. And this is just a begining.
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
Also the previous procidures generate scaffold in memorey. Lets generate the structure to be accessed locally.
class BlogController < ApplicationController
#scaffold :post
end
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.
Sad that lets disable scaffolding.
class BlogController < ApplicationController
#scaffold :post
end
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
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
def list
@posts = Post.find(:all) #Equal to SELECT * FROM posts
end
<h1> Blog </h1>
<% for post in @posts %>
<p><strong><%= post.title %></strong></p>
<p><%= post.body %></p>
<% end %>
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
"post"=>{"body"=>"This is a body for the third post", "title"=>"Post # 3"}
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
<%= error_messages_for 'post' %>
<%= start_form_tag :action => "create" %>...
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
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.
#change
<p><strong><%= post.title %></strong></p>
#to
<p><strong><%= link_to post.title,:action => "show",:id => post.id %></strong></p>
<p><strong><%= @post.title %></strong></p>
<p><%= @post.body %></p>
<p><b>Created @:</b><%= @post.created_at %>
def show
@post = Post.find(params[:id])
end
Lets generate some more actions.
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
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) =======================================
class Post < ActiveRecord::Base
has_many :comments
validates_presence_of :title,:body
end
class Comment < ActiveRecord::Base
validates_presence_of :name,:email,:body
belongs_to :post
end
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?
<%= 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 %>
<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>
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
Usage of ajax is another Rail’s beauty. Lets ajax our comment creation in couple of easy steps.
<%= javascript_include_tag :defaults %>
<div id="comments_div">
<%= render :partial => "comments" %>
</div>
<% 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 %>
#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}) %>
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
...
<%= end_form_tag %>
<span id="indicator" style="display:none"><%= image_tag "indicator.gif" %></span>
<%= form_remote_tag(:update => "comments_div",
:before => "Element.show('indicator')",
:complete => "Element.hide('indicator')",
:url => {:action => "create_comment",:id => @post.id}) %>