Rails 2.0完整体验:Rolling with Rails 2.0 - The First Full Tutorial - Part 1(翻译)
教程来源:http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial
本教程基于Rails 2.02 , 是不是第一篇2.0教程尚未考证。
标题:飞驰在Rails2.0上(Rolling with Rails 2.0) --Rails2.0第一篇完全教程,第一部分
非常高兴我的Rails 2.0 视频有很好的收视率,超过1,500名有独立IP的浏览者收看。那个想法是展现Rails2.0非常快的优点,展示了在不到30分钟的时间里能做些什么。
现在,我把视频分解成几个主要模块,然后做成了围绕Rails2.0非常快特征的第一个的完整教程。像其他的教程一样,这也没有完全100%的覆盖Rails2.0的知识点,仅仅是一些主要特征粘在一起的应用。 我推荐检查下Peepcode's Rails2 PDF 和 Ryan Bates 的 Railscasts.com 以获得更多详细信息。
这教程分为两部分,查看第二部分,点击这里。
现在我们开始。
1.认识一下环境
这个教程是连接前面那些已经有一些Rails1.2基础的人。请查阅一些关于Rails1.2有用的好的教程在互联网上(没有也没关系,Step by step 嘛)。
首先你得更新你的Gems:
Z:\ruby>gem install rails --include-dependencies
你可能最好还要升级一下你的RubyGems。
关于安装Ruby,Gem管理器,Rails等网上有非常多的教程,不多叙述。
首先要做的第一件事,让我建立一个新的Rails 应用程序:
切换到你的Ruby on Rails 工作目录,如果基于MySql数据库就键入以下命令:
G:\rlab>rails -d mysql blog
如果使用默认的sqlite3数据库则键入:
G:\rlab>rails blog
这将创建我们的通用Rails项目目录结构.搭建这个环境后要注意到的第一件事情,我们现在有了这个主结构:
.config\environment.rb
.config\initializers\inflections.rb
.config\initializers\mime_types.rb
在blog\config\initializers目录内的所有事情载入的时候都会在同一时间初始化environment.rb,因此当你在你的项目里用了一些不同的plugins 和 gems后,去维护这个environment.rb文件的护理工作会变得混乱和困难。现在我们有一个简单的办法来模块化我们的配置。
2.数据库
第二件事,我们要配置一下我们的数据库。首先用同样的方法配置blog\config\database.yml:(应注意yml文件中,“:”后面的空格)
development:
adapter: mysql
encoding: utf8
database: blog_development
username: root
password: 123
host: localhost
test:
adapter: mysql
encoding: utf8
database: blog_test
username: root
password: 123
host: localhost
production:
adapter: mysql
encoding: utf8
database: blog_production
username: root
password: 123
host: localhost
注意这里,你有个‘encoding’选项默认它设置成了 utf8 。Rails 应用程序最好是用它自己默认装载的KCODE = true , 意思是说在它默认启动的的时候就已经支持Unicode(无符号字符)了,这会非常好。但是这个'encoding' 配置最好是有一个新的用法,每当Rails连接数据库的时候它将告诉它设置'encoding',像说'SET NAMES UTF8'.
我们能这样DRY(Don't Repeat Yoursel) database.yml
defaults: &defaults
adapter: mysql
encoding: utf8
username: root
password: 123
host: localhost
development:
database: blog_development
<<: *defaults
test:
database: blog_test
<<: *defaults
production:
database: blog_production
<<: *defaults
看,多好。我们最好做一个新的Rake工作。他们中一些与数据库有关的:
- db:charset 检索当前环境下数据库的字符设置
- db:collation 检索当前环境下数据库的校对
- db:create 用config\database.yml中的定义创建当前 RAILS_ENV 项目环境下的数据库
- db:create:all 用config\database.yml中的定义创建所有数据库
- db:drop 删除当前 RAILS_ENV项目环境中的数据库
- db:drop:all 删除所有在 config\database.yml中定义的数据库
- db:reset 从db\schema.rb中为当前环境重建数据库(先删后建).
- db:rollback 回滚(清华出版社一本SQLSERVER书的名词[很奇怪为什么不直接用滚回])数据库到前一个版本. 指定回滚到哪一步要用 STEP=n 参数
- db:version 检索当前模式下的版本
我们有一个很好的远程管理支持,在老版本的Rails里,现在我们就要登录到我们的数据库管理员控制台去手工创建数据库。但现在,我们能这样简单的做:
G:\rlab\blog>rake db:create:all
注意切换到blog目录下,以后我们的命令操作基本上是基于这个目录,所以下面的文章中就不带目录直接写命令了。如果我们要从刻痕处开始,我们能用 db:drop:all。之后在开发中我们能用db:rooback 到没有操作的最近的移植(migration)文件。
3.(性感|苗条)?(Sexyness)
当数据库设置和准备开始后,我们就能建立我们的第一笔资料。记住现在Rails 2.0 默认是RESTful(GOOGLE有解释)。
ruby script\generate scaffold Post title:string body:text
这儿仅仅不同的是"scaffold"行为,像前面我们用的"scaffold_resource",老的非RESTful的scaffold已经不用了。在ActionController类里面没有了"scaffold"方法,它已经默认动态组装到你的空控制器里了。所以,现在每个脚手架scaffold我们已经RESTful啦。
它将创建"嫌疑惯犯"('The Usual Suspects' 著名美国电影,译名有《非常嫌疑犯》《嫌疑惯犯》)们:Controller , Helper, Model , Migration ,Unit Test, Functional Test.
主要的不同是在 Migration 文件里:
1class CreatePosts < ActiveRecord::Migration
2 def self.up
3 create_table :posts do |t|
4 t.string :title
5 t.text :body
6
7 t.timestamps
8 end
9 end
10
11 def self.down
12 drop_table :posts
13 end
14end
我们把这个叫 Sexy Migrations ,是"Err the Blog"发明的,首先只是作为一个插件,后来这方法很多人都觉得不错就加进了核心。理解了这个好的方法后,再看下和Rails1.2的migration哪里不同:
2 def self.up
3 create_table :posts do |t|
4 t.column :title, :string
5 t.column :body, :text
6 t.column :created_at, :datetime
7 t.column :updated_at, :datetime
8 end
9 end
10
11 def self.down
12 drop_table :posts
13 end
14end
摆脱了't.column'的重复,现在用't.column_type'格式和把自动时间列集中在了一个't.timestamps'语句里。没有改变任何行为,仅仅是把代码搞苗条了。
好,现在我们像前面一样的运行migration:
rake db:migrate
以前我们要回滚一个migration需要这样做:
rake db:migrate VERSION=xxx
XXX就是我们要返回的那个版本号,现在我们能简单发号指令:
rake db:rollback
那确实,很好,很优美!都设置好后,我们就能像前面做的一样启动我们的服务器看下产生的页面:
ruby script\server
这将装载到 Mongrel, Webrick 或者 Lightpd等任一WEB服务器的3000端口, 和以前一样我们有同样的首页,显现出index.html页。在视频教程里的一则小道信息我没展出来的在这:
ActionController::Routing::Routes.draw do |map|
map.root :controller => 'posts'
map.resources :posts
end
这里有条新语句:'map.root' ,和 map.connect ,:controller => 'posts' 的效果一样。这语句没做什么其他的事仅仅是把Routes变的精练了点。当你设置好了这个后,别忘记了删除 blog\public\index.html文件。现在根URL将总是定位到 Posts 控制器。
注意这里图片里显示的那些记录是点New post发布之后的。
你看,和以前相比每件事情感觉都简单多了。所有的脚手架模板都一样,你能浏览,创建新行等等。
4.路由嵌套
好,让我们来建一个发表同步评论的资源(表格Table)。这将完成我们的资料库(数据库Database)创建。
ruby script\generate scaffold Comment post:references body:text
rake db:migrate
同样的事情:在命令行里创建脚手架资源(scaffold the resource),配置字段名和数据格式,migration文件就被设置了。注意另一个小添加:关键字“references” 。当我的朋友Arthur 提醒我,这会让migrations更苗条。比较一下,以前的方法我们是这样做的:
ruby script\generate scaffold Comment post_id_:integer body:text
外键仅仅是实现了些细节,没关系,看一下新的migration文件(blog\db\migrate\002_create_comments.rb):
create_table :comments do |t|
t.references :post
t.text :body
t.timestamps
end
end
class Post < ActiveRecord::Base
has_many :comments
end
# app\models\comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
http://localhost:3000/posts/1/comments/new
http://localhost:3000/posts/1/comments/3
意味着:我们能看到详细POST下面的Comments,但scaffold生产器只能去生产下面的这些URL:
http://localhost:3000/comments/new
http://localhost:3000/comments/3
这是因为在路由文件(config\routes.rb)里面有:
map.resources :comments
map.root :controller => 'posts'
map.resources :posts
end
让我们把他们拧一下,就像是在一个model里面一样,我们能创建一种嵌套路由(Nested Route):
ActionController::Routing::Routes.draw do |map|
map.root :controller => 'posts'
map.resources :posts, :has_many => :comments
end
像这样我们就能看到前面说的嵌套的URL。
Rails 就会这样分析它:
· 载入CommentsController
· 设置参数[:post_id] = 1
· 既然情况这样,叫‘index’行动
我们已经准备好让Comments controller被嵌套了,如此一来我们将要做下面的这样一些改变:
class CommentsController < ApplicationController
before_filter :load_post
......
def load_post
@post=Post.find(params[:post_id])
end
end
这将设置好@post的所有行为准备好做 Comments controller。现在我们就还要做下面的这些替换(app\controllers\comments_controller.rb):
改变前: | 替换为: |
Comment.find | @post.comments.find |
Comment.new | @post.comments.build |
redirect_to(@comment) | redirect_to(@post, @comment) |
redirect_to(comments_url) | redirect_to(post_comments_url) |
这样就会让Comments controller准备好。现在我们更改4个视图(view)文件,如果你打开 new.html.erb 和 edit.html.erb 你将发现以下这些新特征:
form_for(@comment) do |f|
end
这是我们以前1.2里面做这些事的旧语句:
form_for(:comment, :url => comments_url) do |f|
end
# old edit.rhtml
form_for(:comment, :url => comment_url(@comment),
:html => { :method => :put }) do |f|
end
注意在new.rhtml和edit.rhtml 中一样的语句form_for。这样是因为Rails能推断根据名为@comment类的实例做什么,但现在用了嵌套路由,comments依靠Post了,所以我们要做:
form_for([@post, @comment]) do |f|
end
Rails 会很快明白嵌套路由里面的这些数组表达的内容,将会检查routes.rb文件,计算出名为
post_comment_url(@post, @comment) 的路由。
让我们解释下叫这些名字的路由先,当我们设置route.rb文件里的路由资源的时候,我们获得了这些名字的路由:
路由名 |
HTTP 传送方式 |
控制器行为 |
comments |
GET |
index |
comments |
POST |
create |
comment(:id) |
GET |
show |
comment(:id) |
PUT |
update |
comment(:id) |
DELETE |
destroy |
new_comment |
GET |
new |
edit_comment(:id) |
GET |
edit |
"7种行为统治他们所有人" :-)
你能给他们加后缀 "path"或者"url" ,不同点就是:
comments_url |
http://localhost:3000/comments |
comments_path |
/comments |
最后你能给他们加前缀“formatted” ,会给你:
formatted_comments_url(:atom) |
http://localhost:3000/comments.atom |
formatted_comment_path(@comment, :atom) |
/comments/1.atom |
现在,Comments嵌套到Post里面去了,我们有责任添加前缀'post' 。在Rails1.2里,这个前缀是可选的,他能通过数字和参数告诉你通往不同的post helper,但这会搞得有些含糊,所以现在必须添加前缀,像这样:
route |
HTTP verb |
URL |
post_comments(@post) |
GET |
/posts/:post_id/comments |
post_comments(@post) |
POST |
/posts/:post_id/comments |
post_comment(@post, :id) |
GET |
/posts/:post_id/comments/:id |
post_comment(@post, :id) |
PUT |
/posts/:post_id/comments/:id |
post_comment(@post, :id) |
DELETE |
/posts/:post_id/comments/:id |
new_post_comment(@post) |
GET |
/posts/:post_id/comments/new |
edit_post_comment(@post, :id) |
GET |
/posts/:post_id/comments/edit |
总的来说,这样一做,我们就能使comments视图运转起来像嵌套在post里面一样。所以我们需要改变一下默认的脚手架生产代码去适应嵌套:
<% form_for([@post, @comment]) do |f| %>
<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit button_name %>
</p>
<% end %>
<h1>Editing comment</h1>
<%= error_messages_for :comment %>
<%= render :partial => @comment,
:locals => { :button_name => "Update"} %>
<%= link_to 'Show', [@post, @comment] %> |
<%= link_to 'Back', post_comments_path(@post) %>
<h1>New comment</h1>
<%= error_messages_for :comment %>
<%= render :partial => @comment,
:locals => { :button_name => "Create"} %>
<%= link_to 'Back', post_comments_path(@post) %>
<p>
<b>Body:</b>
<%=h @comment.body %>
</p>
<%= link_to 'Edit', [:edit, @post, @comment] %> |
<%= link_to 'Back', post_comments_path(@post) %>
<h1>Listing comments</h1>
<table>
<tr>
<th>Post</th>
<th>Body</th>
</tr>
<% for comment in @comments %>
<tr>
<td><%=h comment.post_id %></td>
<td><%=h comment.body %></td>
<td><%= link_to 'Show', [@post, comment] %></td>
<td><%= link_to 'Edit', [:edit, @post, comment] %></td>
<td><%= link_to 'Destroy', [@post, comment],
:confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New comment',
new_post_comment_path(@post) %>
·注意这里我创建了一个partial DRY的new和edit表单,但重视替换 :partial => ‘comment’,我做了":partial => @comment ",他们将从类名里推断partial,如果我们通过了一个集合,他将做相当于旧语句":partial, :collection"的事。
·我能用post_comment_path(@post, @comment)或者简单的[@post, @comment]
·更要重视的是别忘记了路由背后的任何名字。
最后,链接Comments列表页面到Post页面,我需要做:
<%= link_to 'Comments', post_comments_path(@post) %>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
我仅仅添加了一个链接,让我再来看看它看起来会像什么:
完成视图
好,看起来不错,但现在它还不太像一个将要运行的博客,post页面最好还应该显示已经有的comments列表和一个提交新注解的表单,所以我们现在还需要进行一些小小改编,和传统的 Rails相比没有什么新改变,让我们开始改视图:
<p>
<b>Title:</b>
<%=h @post.title %>
</p>
<p>
<b>Body:</b>
<%=h @post.body %>
</p>
<!-- #1 -->
<% unless @post.comments.empty? %>
<h3>Comments</h3>
<% @post.comments.each do |comment| %>
<p><%= h comment.body %></p>
<% end %>
<% end %>
<!-- #2 -->
<h3>New Comment</h3>
<%= render :partial => @comment = Comment.new,
:locals => { :button_name => 'Create'}%>
<%= link_to 'Comments', post_comments_path(@post) %>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
更多注释:
· 这个迭代程序里面没有任何新东西,仅仅是把注解列出来
· 这里通过@comment变量对partial语句
最后的一点调整:当提交一个新注解之后我们希望回到它的POST页面,这就需要改写下comments_controller.rb中的路由
把app/controllers/comments_controller.rb中的旧路由:
redirect_to(@post, @comment)
替换成新路由:
redirect_to(@post)
路由命名空间:
好,我们现在已经有一个露骨的迷你博客了,有点15分钟视频里面的大卫在2005年做的经典博客的仿制品的味道了。现在,让我们更进一步,公布的文章(post)不可以被所有人编辑,我们需要一个Administration在我们的网站里面进行管理工作。让我们创建一个新的控制器:
ruby script\generate controller Admin::Posts
Rails2.0现在支持命名空间,这将创建一个叫"app/controllers/admin"的子目录。
我们要做这些事:
1,创建一个新的路由
2,拷贝所有旧Posts controller里面的actions到新的Admin::posts,只留下"index和"show"这两个。
3,拷贝所有旧Posts views到app/views/admin/* 只留下旧Posts views的"index"和"show"这两个,意思是最好把旧posts views下面new和edit视图都删除掉。
4,改编actions和views我们仅仅拷贝过来它们还不能明白应该怎么和admin controller一起工作。
首先的首先,让我们再编辑路由文件(config\routes.rb):
admin.resources :posts
end
实际上这意味着我们现在有的作为posts的路由名字带了个前缀"admin",这将消除新添加的的admin posts路由和旧的posts路由之间的歧义,像这样:
posts_path |
/posts |
post_path(@post) |
/posts/:post_id |
admin_posts_path |
/admin/posts |
admin_post_path(@post) |
/admin/posts/:post_id |
现在让我们把actions从旧post controller拷贝到admin postcontroller里并改变使之适应新的namespace:
def create
# old:
format.html { redirect_to(@post) }
# new:
format.html { redirect_to([:admin, @post]) }
end
def update
# old:
format.html { redirect_to(@post) }
# new:
format.html { redirect_to([:admin, @post]) }
end
def destroy
# old:
format.html { redirect_to(posts_url) }
# new:
format.html { redirect_to(admin_posts_url) }
end
别忘记了删掉app/controllers/posts_controller.rb里面的所有方法,只留下index 和show这两个。
现在,让我们来拷贝这些视图(也可以直接在windows里面拖放):
copy app\views\posts\*.erb app\views\admin\posts
del app\views\posts\new.html.erb
del app\views\posts\edit.html.erb
现在,让我们来编辑app/views/admin/posts下面的那些视图(views):
<h1>Editing post</h1>
<%= error_messages_for :post %>
<% form_for([:admin, @post]) do |f| %>
<% end %>
<%= link_to 'Show', [:admin, @post] %> |
<%= link_to 'Back', admin_posts_path %>
<!-- app/views/admin/posts/new.html.erb -->
<h1>New post</h1>
<%= error_messages_for :post %>
<% form_for([:admin, @post]) do |f| %>
<% end %>
<%= link_to 'Back', admin_posts_path %>
<p>
<b>Title:</b>
<%=h @post.title %>
</p>
<p>
<b>Body:</b>
<%=h @post.body %>
</p>
<%= link_to 'Edit', edit_admin_post_path(@post) %> |
<%= link_to 'Back', admin_posts_path %>
<% for post in @posts %>
<tr>
<td><%=h post.title %></td>
<td><%=h post.body %></td>
<td><%= link_to 'Show', [:admin, post] %></td>
<td><%= link_to 'Edit', edit_admin_post_path(post) %></td>
<td><%= link_to 'Destroy', [:admin, post],
:confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New post', new_admin_post_path %>
差不多做好了,如果你现在测试 http://localhost:3000/admin/posts 它将完全能好好工作。但是,它的样子看起来将是有点丑,这是因为我们没有全局的应用程序外观(app layout),当我们做第一个脚手架的时候,Rails产生器单独给Post和Comment做了个外观,所以,我们现在删除他们然后建立现在这样的一个通用的外观:
把app\views\layouts\posts.html.erb文件改名为:application.html.erb,再删掉app\views\layouts\comments.html.erb
这样仅仅保留了从早先的旧Posts controllers的 index 和 show 页面,它们仍然有链接方法,我们要删掉,所以让我们把这些链接撕断:
<h1>My Great Blog</h1>
<table>
<tr>
<th>Title</th>
<th>Body</th>
</tr>
<% for post in @posts %>
<tr>
<td><%=h post.title %></td>
<td><%=h post.body %></td>
<td><%= link_to 'Show', post %></td>
</tr>
<% end %>
</table>
<p>
<b>Title:</b>
<%=h @post.title %>
</p>
<p>
<b>Body:</b>
<%=h @post.body %>
</p>
<!--#comments list -->
<% unless @post.comments.empty? %>
<h3>Comments</h3>
<% @post.comments.each do |comment| %>
<p><%= h comment.body %></p>
<% end %>
<% end %>
<h3>New Comment</h3>
<%= render :partial => @comment = Comment.new,
:locals => { :button_name => 'Create'}%>
<%= link_to 'Back', posts_path %>
基本的HTTP身份验证:
这儿有好几种方式进行身份验证,有个插件叫restful_authentication 使用得非常广泛。
但是,我们不需要搞得每件事情都异常复杂。Rails2.0给我们提供了一种强大的验证方法。这理念就是,让我们使用HTTP,已经给我们的HTTP Basic Authentication.它的缺点就是你将要使用SSL当成为产品的时候。但是,当然你能使用任何方法做到。HTML 表单验证在没有SSL的时候是没有被保护的。
好,我们现在编辑我们的Admin::Posts controller 添加身份验证:
class Admin::PostsController < ApplicationController
before_filter :authenticate
def authenticate
authenticate_or_request_with_http_basic do |name, pass|
#User.authenticate(name, pass)
name == 'admin' && pass == 'admin'
end
end
我们实现这个方法还增加点保密的趣味"authenticate_or_request_with_http-basic" 方法让我们配置好一个块。它在浏览器里给我们这个输入用户名和密码的对话框,我们就要使用用户名和密码才能登录管理博客的数据库,但这是个非常简单的例子,里面的用户名和密码用了明码且无需到数据库验证,真正的产品需要进行其他工作,但是你获得了身份验证这个好主意。