以下这篇文章翻译自这里 ,还是新人翻译不好请大家多多指教.
我很高兴的看到 我的
rails2.0视频教程被广泛接受。超过1500人次看过它,做他的目的是想简单快速地介绍下rails2.0,展示下那些可以在30分钟以内做到的事。
现在,我将要把那段视频分成几个主要的部分,并且建立一个包含一些新特性的逐步讲解的rails2.0教程.
像其他教程一样,他并不能涵盖rails2.0 100%的特性,详细的我推荐你去看看
Peepcode’s Rails2 PDF 和Ryan Bates
Railscasts.com .
这是一个两部分教程,第二部分请点击
这里 关于这个教程的全部源代码在
这里。
让我们开始把。
Recognizing the Environment
这个教程是面向那些已经掌握了一些rails1.2知识的人,请参考互联网上许多优秀的关于 rails1.2的教程。
一开始你所要做的事更新你的gems:
sudo gem install rails --include-dependencies
你可能还想要更新的你的 RubyGems:
sudo gem update --system
首先,我们创建一个rails应用程序:
rails blog
这将会创建我们常见的rails 文档结构。首先注意到的是开发环境。这是我们现在拥有的主要结构:
* config/environment.rb
* config/initializers/inflections.rb
* config/initializers/mime_types.rb
所有在 config/initializers 文件夹里的东西将会和environment.rb 同时被载入。那是因为当你的项目使用了许多不同的插件和gems的时候,environment.rb会变得越来越臃肿 并且难以维护。现在我们有了一个更简单的方式去模块化管理我们的配置。
Database
第二个要配置的东西是我们的数据库。这和以前要干的事没两样,都是在 config/database.yml:
development:
adapter: mysql
encoding: utf8
database: blog_development
username: root
password: root
socket: /opt/local/var/run/mysql5/mysqld.sock
test:
adapter: mysql
encoding: utf8
database: blog_test
username: root
password: root
socket: /opt/local/var/run/mysql5/mysqld.sock
production:
adapter: mysql
encoding: utf8
database: blog_production
username: root
password: root
socket: /opt/local/var/run/mysql5/mysqld.sock
注意到现在你有一个‘encoding’ 选项默认设置成 utf-8. rails 应用程序也是默认读取 KCODE=true, 意味着他一开始就应经支持了UNICODE了,这是相当棒的。但"encoding"配置还有另个用途:每个连接到数据库的rails都会被告之使用encoding的设置。就像是设置了 '"SET NAMES UTF8"
一个小技巧用来对 database.yml实现 DRY 原则的是:
defaults: &defaults
adapter: mysql
encoding: utf8
username: root
password: root
socket: /opt/local/var/run/mysql5/mysqld.sock
development:
database: blog_development
<<: *defaults
test:
database: blog_test
<<: *defaults
production:
database: blog_production
<<: *defaults
好多了,我们有个新的rask任务。并且其中的一些和数据库有关:
db:charset Retrieves the charset for the current environment’s database
db:collation Retrieves the collation for the current environment’s database
db:create Create the database defined in config/database.yml for the current RAILS_ENV
db:create:all Create all the local databases defined in config/database.yml
db:drop Drops the database for the current RAILS_ENV
db:drop:all Drops all the local databases defined in config/database.yml
db:reset Drops and recreates the database from db/schema.rb for the current environment.
db:rollback Rolls the schema back to the previous version. Specify the number of steps with STEP=n
db:version Retrieves the current schema version number
我们有更好的数据库管理支持。在以前,我们得登入到数据库控制台然后手动创建这些数据库,现在我们只要简单地:
rake db:create:all
如果我们现在想要重头做起,我们可以 db:drop:all. 如果是在开发当中我们可以利用 db:rollback 回退到上个版本。
Sexyness(性感)
在数据库的设置好了下面我们就可以创建我们的第一个资源。记得吧,现在 rails2.0是默认 RESTful 的。(for brazilians: 我也另外写了篇关于restful 的教程)。
.
/script/generate scaffold Post title:string body:text
这里唯一不同的是'scaffold'的用处就和我们以前用的'scaffold_resource'一样,和那个旧的并不是 restful 的 scaffold 也已经没了。你现在也没有 ActionController类方法'scaffold'来动态创建一个带有默认action的空的控制器了。所以任何我们所scaffold 的东西都变成 RESTful的了。
他会创建那些常见的东西:Controller, Helper, Model, Migration, Unit Test, Functional Test.
主要的不同是在迁移文件:
# db/migrate/001_create_posts.rb
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end
end
def self.down
drop_table :posts
end
end
这叫做 性感迁移(Sexy Migrations),首先发明自 “Err the Blog” 作为一个插件随之进入到了rails内核中。了解他们有何异同最好的方式就是去看看他在rails1.2中长什么样:
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.column :title, :string
t.column :body, :text
t.column :created_at, :datetime
t.column :updated_at, :datetime
end
end
def self.down
drop_table :posts
end
end
他去除了重复的't.column',现在使用‘t.column_type’的格式并且自动加上的时间戳被浓缩成了一行语句‘t.timestamps’.并没有改变什么行为,只是让代码更加"性感"些
现在,像以前一样运行一个迁移任务
rake db:migrate
在以前,如果我要回退到某一个数据迁移我需要这样做:
rake db:migrate VERSION=xxx
'xxx'是代表我们想要回退到的版本号,现在我们仅仅需要:
rake db:rollback
更加的简洁优雅,这是肯定的。一切都设置好了,现在我们可以像以前一样启动服务器,看看生成的页面。
./script/server
他会自动载入不管是在 Mongrel, Webrick 或 Lightpd 在 3000 端口。我们和以前一样拥有一个根页面,为 index.html.有一个小点我在视频里面没有提到的是:
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.root :controller => 'posts'
map.resources :posts
end
这里有一个新的语句‘map.root’,该语句和 “map.connect ’’, :controller => ‘posts’.有同样的效果。只是一点小小的花招并没有做什么大动作只是让路由文件看起来更优美些。一旦设置了它,别忘了了删除 public/index.html 文件。 根URL就总会指向POSTS控制器。
正如你所看到的,一切都和以前一样。所有的脚手架模板都还是一样。你可以浏览一下,创建一些新行等等。
嵌套路由
让我们来创建一些和POST有关的COMMENT。他会完成创建我们 blog的资源:
./script/generate scaffold Comment post:references body:text
rake db:migrate
和上面一样:scaffold一下资源,在命令行中设置组名和数据类型然后迁移文件就会自动设置好。注意到另外一个新增的东西:关键字”references“ 正如 许多朋友提醒我的,这会使数据迁移更性感。
比较起来,这是以前做这件事用的方法:
./script/generate scaffold Comment post:references body:text
外键只是一个无关紧要的实现细节,看看新的数据迁移文件:
def self.up
create_table :comments do |t|
t.references :post
t.text :body
t.timestamps
end
end
看这里关于新的关键字” references“的细节。所以,运行 db:migrate 将在数据库中创建表。
然后我们设置 ActiveRecord模型让他们和彼此互相关联,像这样:
# app/models/post.rb
class Post < ActiveRecord::Base
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
end
好了,这里没有什么新东西了,我们已经知道怎么处理 ActiveRecord关联了 ,但我们依旧工作在RESTFUL资源环境下。在新的RAILS方式里,我们可以拥有像这样的url:
http://localhost:3000/posts/1/comments
http://localhost:3000/posts/1/comments/new
http://localhost:3000/posts/1/comments/3
意思是 从这个具体的POST里取出相关的comment,脚手架生成器只准备了如下这种url:
http://localhost:3000/posts/1
http://localhost:3000/comments/new
http://localhost:3000/comments/3
那是因为在config/routes.rb 中:
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :comments
map.root :controller => 'posts'
map.resources :posts
end
让我们稍微调整一下,就像在model中,我们可以这样调用一个嵌套路由:
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.root :controller => 'posts'
map.resources :posts, :has_many => :comments
end
就像这样! 现在我们可以想上面这样嵌套url. 首先要理解的是当我打入这样的URL:http://localhost:3000/posts/1/comments
rails 会将它解析成这样:
* 载入控制器
* 设置 params[:post_id] = 1
* 在这种情况下,调用' index ' 的action.
我们必须准备让CommentsController被嵌套。所以这是我们接下来要去修改的:
class CommentsController < ApplicationController
before_filter :load_post
...
def load_post
@post = Post.find(params[:post_id])
end
end
这是的 @post已经在所有的术语comments控制器中的action中设置好了。现在我们必须做这些修改:
之前 修改后
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(@post))
这是的comment 控制器准备好了。现在让我们改变一下4个在 app/views/comments中的视图。
如果你打开不管是 new.html.erb 或者是 edit.html.erb你将会注意到如下这些新的特性:
# new edit.html.erb and new.html.erb
form_for(@comment) do |f|
...
end
这是新的定义方式:
# old new.rhtml
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
注意到同样的 form_for 语句是怎样适应 'new' 和 'edit' 的情况。
这是因为Rails可以根据 @comment模型实例的类名去推断出做些什么,但是现在
对于嵌套路由来说,comment是依赖于post,所以我们必须这样做:
# new edit.html.erb and new.html.erb
form_for([@post, @comment]) do |f|
...
end
Rails会变得足够聪明去辨识这个数组是表示一个嵌套路由,它会去检查 routes.rb 并且找出
他是post_comment_url(@post, @comment)的具名路由
让我们先来解释下具名路由。当我们在routes.rb设置资源路由时,我们可以得到下列具名路由:
oute HTTP verb Controller Action
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
“七个 Action足以对付一切 …” :-)
你可以给他们加上 'path'或是'url'的后缀,不同在:
comments_url http://localhost:3000/comments
comments_path /comments
最后你可以给他们加上 'formatted'的前缀,给你:
comments_url http://localhost:3000/comments
comments_path /comments
现在,当comment已经嵌套在post里面,我们必须加上 'post'前缀,在 rails1.2里,这个前缀是可选择的,
他将能够依据传到具名路由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里一样。
所以我们还必须对从默认脚手架生成的代码到内嵌的表格中的具名路由做些改变:
<!-- app/views/comments/_comment.html.erb -->
<% form_for([@post, @comment]) do |f| %>
<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit button_name %>
</p>
<% end %>
<!-- app/views/comments/edit.html.erb -->
<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) %>
<!-- app/views/comments/new.html.erb -->
<h1>New comment</h1>
<%= error_messages_for :comment %>
<%= render :partial => @comment,
:locals => { :button_name => "Create"} %>
<%= link_to 'Back', post_comments_path(@post) %>
<!-- app/views/comments/show.html.erb -->
<p>
<b>Body:</b>
<%=h @comment.body %>
</p>
<%= link_to 'Edit', [:edit, @post, @comment] %> |
<%= link_to 'Back', post_comments_path(@post) %>
<!-- app/views/comments/index.html.erb -->
<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) %>
提几点:
注意到我创建了一个局部模板去 DRY(不要重复你自己) new 和 edit中表格。但请注意,
并非使用 :partial=> 'comment' 而使用 :partial=>@comment.
然后再一次他能够从类名推断出局部模板的名字。如果我们传进一个集合他会将其转换成旧的语句 ':partial,:collection'
我可以使用post_comment_path(@post, @comment),或是更简单的 [@post, @comment]
请注意不要忘记背后的那些具名路由。
最后,最好将comment列表页的链接放到POST视图里,我们这样做:
<!-- app/views/posts/show.html.erb -->
<%= link_to 'Comments', post_comments_path(@post) %>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
我只是加了个链接,让我们瞧瞧他看起来怎么样:
完成视图
好了,看起来还不错。但这并不像 一个blog的行为! POST的show 视图应该有些comment罗列在那里,并且新建评论的表格应该也在那里。所以让我们做些小改变。这里没有什么新的,只是传统的rails。让我们从视图开始:
<!-- app/views/posts/show.html.erb -->
<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 %>
再提几点:
1.迭代中并没有什么新的东西,只是列出一些评论
2.再一次,我们传进@comment给 partial语句
最后一个小调整,任何时候我们创建一个新COMMENT(译者注:原文为POST,估计是作者笔误,多谢woody_420420兄提醒.),我们会想回到同样的POST中的show视图,所以我们改变CommentController的行为像这样:
# app/controllers/comments_controller.rb
# old redirect:
redirect_to([@post, @comment])
# new redirect:
redirect_to(@post)
命名空间路由
好了,现在我们有一个骨瘦如柴的迷你博客像是模仿经典david在2005念做的一段15分钟创建一个BLOG的视频。现在我们更进一步:Post不应该让所有的人都可以去编辑他,我们的网站需要一个管理部分。让我们为它创建一个控制器:
./script/generate controller Admin::Posts
rails2.0 现在支持命名空间。他会创建一个子目录叫 app/controllers/admin.
我们所要作的是:
1.创建一个新的路由
2.把所有在旧的 posts控制器中的 action复制到新的 Admin::posts中
3.复制所有旧的posts视图到app/views/admin* ,在旧的posts 控制器中只留下 ‘index’和‘show’ 这两个action,
这意味着也要删除new 和 edit.
4.修改 我们刚刚复制的actions和views,让他能够知道他是在admin控制器中
首先,我们再次编辑 config/routes.rb:
map.namespace :admin do |admin|
admin.resources :posts
end
这意味着我们现在有了带着 'admin'前缀的 posts的具名路由。这会使旧的POST路由和新的
admin post路由不会想混。像这样:
posts_path /posts
post_path(@post) /posts/:post_id
admin_posts_path /admin/posts
admin_post_path(@post) /admin/posts/:post_id
现在让我们从旧的POST控制器中拷贝ACTION并修改路由地址去适应新的命名空间:
# app/controllers/admin/posts_controller.rb
...
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’两个方法。
现在,让我们拷贝视图(假设你的 shell已经在项目的根文件夹下):
cp app/views/posts/*.erb app/views/admin/posts
rm app/views/posts/new.html.erb
rm app/views/posts/edit.html.erb
现在让我们编辑 app/views/admin/posts中的视图:
<!-- app/views/admin/posts/edit.html.erb -->
<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 %>
<!-- app/views/admin/posts/show.html.erb -->
<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 %>
<!-- app/views/admin/posts/index.html.erb -->
...
<% 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,他应该能正常的工作。但看起来却很丑陋,那是因为我们没有全局的布局模板。
当我们生成第一个脚手架时,rails为post和 comment生成各自相关的布局模板。所以让我们删掉他们并且创建一个通用的:
cp app/views/layouts/posts.html.erb \
app/views/layouts/application.html.erb
rm app/views/layouts/posts.html.erb
rm app/views/layouts/comments.html.erb
然后让我们改一下标题:
<!-- app/views/layouts/application.html.erb -->
...
<title>My Great Blog</title>
...
他只剩下旧的在posts控制器里的'index' 和 ‘show’页面,他们仍然拥有我们有链接到我们删除过的方法的链接,所以我们可以删掉他们。
<!-- app/views/posts/index.html.erb -->
<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>
<!-- app/views/posts/show.html.erb -->
<p>
<b>Title:</b>
<%=h @post.title %>
</p>
<p>
<b>Body:</b>
<%=h @post.body %>
</p>
<% 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://localhost:3000/admin/posts ,就能看到一切都工作的很好。但是,我们依旧少了样东西:一个系统管理部分不应该公开。现在你可以进去编辑任何东西了。我们需要认证。
HTTP基本认证
有许多实现验证和授权的方式。一个用的很广泛的插件是
restful_authentication.
但是在这里我们不想做些太炫的东西。而RAILS2.0给了我们一个很好的方式去做验证。这个就是:我们用HTTP已经给我们的东西:HTTP基本认真。
缺点是:当在生产环境下时你肯定会想用SSL。当然,你还是会这样做。HTML 表单验证并没有让SSL保护。
所以,让我们编辑我们的Admin::Posts控制器,添加验证功能:
# app/controllers/admin/posts.rb
class Admin::PostsController < ApplicationController
before_filter :authenticate
...
def authenticate
authenticate_or_request_with_http_basic do |name, pass|
#User.authenticate(name, pass)
name == 'akita' && pass == 'akita'
end
end
end
你已经知道'before_filter'是做什么用的了:他在任何 控制器中的action执行前先执行 configured方法。如果你在ApplicationController类中设置他会在任何其他控制器执行前先执行。但在这里我们只想要保护 Admin::Posts.
接着我们执行这个方法,但秘密武器是 ‘authenticate_or_request_with_http_basic’方法,该方法让我们设置一个块。他给我们用户在输入中的一个用户名和密码。通常我们需要有一个User模型去验证这些数据,但是对于我们这个超级简单的例子来说我就硬编码这个它,但是你知道你该如何做。