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 文件里:

 0  # db\migrate\001_create_posts.rb
 1
class 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哪里不同:

 1class CreatePosts < ActiveRecord::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页。在视频教程里的一则小道信息我没展出来的在这:

# config\routes.rb
  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):

def self.up
    create_table :comments 
do |t|
      t.references :post
      t.text :body

      t.timestamps
    end
end
看一下新关键字references下的细节:运行rake db:migrate 建立好数据库中的数据表后,我们来配置它涉及到的每一个ActiveRecord 模型(model)
# 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下面的Comments,但scaffold生产器只能去生产下面的这些URL:

http://localhost:3000/posts/1
http://localhost:3000/comments/new
http://localhost:3000/comments/3

这是因为在路由文件(config\routes.rb)里面有:

ActionController::Routing::Routes.draw do |map|
  map.resources :comments

  map.root :controller 
=> 'posts'
  map.resources :posts
  
end

让我们把他们拧一下,就像是在一个model里面一样,我们能创建一种嵌套路由(Nested Route):
#config\routes.rb
ActionController::Routing::Routes.draw 
do |map|
  map.root :controller 
=> 'posts'
  map.resources :posts, :has_many => :comments  
end

像这样我们就能看到前面说的嵌套的URL。
http://localhost:3000/posts/1/comments

Rails 就会这样分析它:
    · 载入CommentsController
    · 设置参数[:post_id] = 1 
    · 既然情况这样,叫‘index’行动
我们已经准备好让Comments controller被嵌套了,如此一来我们将要做下面的这样一些改变:

#app\controllers\comments_controller.rb
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 你将发现以下这些新特征:

new edit.html.erb and new.html.erb
form_for(@comment) 
do |f|
  
end

这是我们以前1.2里面做这些事的旧语句:
# 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

注意在new.rhtml和edit.rhtml 中一样的语句form_for。这样是因为Rails能推断根据名为@comment类的实例做什么,但现在用了嵌套路由,comments依靠Post了,所以我们要做:

new edit.html.erb and new.html.erb
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里面一样。所以我们需要改变一下默认的脚手架生产代码去适应嵌套:
<!-- 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) %>
一些注释:
        ·注意这里我创建了一个partial DRY的new和edit表单,但重视替换 :partial => ‘comment’,我做了":partial => @comment ",他们将从类名里推断partial,如果我们通过了一个集合,他将做相当于旧语句":partial, :collection"的事。
        ·我能用post_comment_path(@post, @comment)或者简单的[@post, @comment]
        ·更要重视的是别忘记了路由背后的任何名字。

最后,链接Comments列表页面到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 %>

我仅仅添加了一个链接,让我再来看看它看起来会像什么:


 

完成视图

 好,看起来不错,但现在它还不太像一个将要运行的博客,post页面最好还应该显示已经有的comments列表和一个提交新注解的表单,所以我们现在还需要进行一些小小改编,和传统的 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 %>

更多注释:
    · 这个迭代程序里面没有任何新东西,仅仅是把注解列出来
    · 这里通过@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):

map.namespace :admin do |admin|
  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:

# 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这两个。
现在,让我们来拷贝这些视图(也可以直接在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):
<!-- 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 它将完全能好好工作。但是,它的样子看起来将是有点丑,这是因为我们没有全局的应用程序外观(app layout),当我们做第一个脚手架的时候,Rails产生器单独给Post和Comment做了个外观,所以,我们现在删除他们然后建立现在这样的一个通用的外观:
    把app\views\layouts\posts.html.erb文件改名为:application.html.erb,再删掉app\views\layouts\comments.html.erb
这样仅仅保留了从早先的旧Posts controllers的 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>
<!--#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://localhost:3000/admin/posts 看到每样事情都在井井有条的工作着,但是我们仍然有一件事没做,那就是现在管理员功能是所有人都可以用的,如果正好跳进去了就可以编辑每样东西!我们需要身份验证。
基本的HTTP身份验证:
这儿有好几种方式进行身份验证,有个插件叫restful_authentication 使用得非常广泛。
但是,我们不需要搞得每件事情都异常复杂。Rails2.0给我们提供了一种强大的验证方法。这理念就是,让我们使用HTTP,已经给我们的HTTP Basic Authentication.它的缺点就是你将要使用SSL当成为产品的时候。但是,当然你能使用任何方法做到。HTML 表单验证在没有SSL的时候是没有被保护的。

好,我们现在编辑我们的Admin::Posts controller 添加身份验证:
# app/controllers/admin/posts_controller.rb
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
你已经知道 before_filter 做什么:在控制器里,它运行在任何配置的行为之前。如果你在应用程序控制器类(ApplicationController class)里设置它,那么它将运行在所有的其他控制器的任何行为之前,但这里我们仅仅需要保护Admin:Post 。

我们实现这个方法还增加点保密的趣味"authenticate_or_request_with_http-basic" 方法让我们配置好一个块。它在浏览器里给我们这个输入用户名和密码的对话框,我们就要使用用户名和密码才能登录管理博客的数据库,但这是个非常简单的例子,里面的用户名和密码用了明码且无需到数据库验证,真正的产品需要进行其他工作,但是你获得了身份验证这个好主意。

代码下载:/Files/x116/blog.rar
posted @ 2008-02-03 18:10  普若伽门  阅读(3141)  评论(2编辑  收藏  举报