Rails教程

文章出处:http://edgeguides.rubyonrails.org/getting_started.html

 

再拾Rails的时候发现已经到3.0.3了, 巨大的改变发现找篇中文教程都找不到,于是索性根据官方教程一边学一边翻译,就当学习笔记。水平有限,不足此处敬请谅解。

 

1. 环境搭建

 

  • Ruby1.8.7+ (那么由于187p248,249的bug可能会导致Rails3崩溃,官方建议使用1.9.2的Ruby版本)
  • RubyGems
  • SQLITE3数据库 (那么这个数据库是Rails内置支持的,我们也可以使用其他数据库,比如MySql5.5)
安装:
1, 安装ruby192, 地址: http://rubyinstaller.org/
2, 安装rails3, $ gem install rails
3, 安装IDE, 个人比较喜欢Aptana3, www.aptana.com

 

 

2. Rails的指导原则和理念

 

  • DRY(Dont Repeat Yourself) - 保持代码清晰高效,这个一直是我喜欢rails的原因之一
  • 约定大于配置 - rails假定你想要做什么以及准备如何做,而不是把任何事情都放到配置文件中去。比如我们说好数据库表的名称应该是模型类名称的小写的复数形式。那么rails会自动把该数据表与其模型类进行关联。
  • REST是WEB应用的最佳模式 - 用resources以及标准的HTTP动作来组织你的应用。

 

3. 创建你的第一个应用

大多数的第一个应用要么是购物车要么就是博客, 我们也不能免俗. 我们将要在下面的内容中打造一个博客,也就是rails经常吹嘘的10分钟打造的博客系统(事实上用不了十分钟)

 

$ rails new blog

 

这将使rails为你创建一个blog工程 (rails -h可以查看rails命令的帮助)

 

$ cd blog

 

我们转到blog目录内。

 

那么在任何情况下,Rails会在你的工作路径内为你创建一个blog的文件夹。本教程的大多数工作都会在app文件夹内操作,这里有一个Rails默认创建应用时创建的文件夹以及文件说明:

文件/文件夹 说明
Gemfile 该文件允许你指定你程序所需要的gem (比如你用到了mysql数据库, 你需要在这里指定 gem "mysql").
README 你的应用程序大体说明. 用这个来告诉他人该程序是干什么的, 等等.
Rakefile 该文件包含了一系列可以在终端运行的命令.
app/ 包含了应用程序的控制器,模型,以及视图. 在本教程中你将专注于该文件夹.
config/ 配置你应用程序的运行时规格,路由,数据库等等.
config.ru 基于服务器的Rack配置,用于启动程序.
db/ 显示你当前数据库的模式,以及数据库迁移.
doc/ 你应用的详细文档.
lib/ 你应用的扩展模块.
log/ 日志.
public/ 唯一可以被访问的文件夹. 你可以保存你的图像,javascript, 样式表(CSS), 以及其他静态文本.
script/ 包含启动你程序的rails脚本,并可以放置其他你用来部署或者运行你程序的脚本.
test/ 单元测试,夹具以及其他测试
tmp/ 临时文件
vendor/ 放置第三方代码的地方. 在一个典型的rails应用中, 这将包括ruby gems, rails源代码 (如果你拷贝到你的应用中的话), 以及包含额外功能的插件.

接下来我们安装需要的gems

 

$ bundle install

(事实上绑定的gems都定义在gemfile中)

 

配置数据库连接:config/database.yml, 默认情况下是连接到SQLite3的, 如果你在使用SQLite3,保持默认就可以了, 如果是MySql,需要改为:

development:
   adapter: mysql
   encoding: utf8
   database: blog_development
   pool: 5
   username: root
   password:

接下来创建数据库。

 

$ rake db:create

 

该rake命令会根据你的数据库配置自动为你创建数据库

 

万事俱备,只欠服务器了

 

$ rails s

(新的script命令变化很大, 现在只需要输入 rails [option] [args] 就可以了)

 

默认情况下 rails 将启动 Webrick 服务器, 赶快打开你的浏览器在地址栏输入 http://localhost:3000/ 查看你的第一个rails程序 吧 :)

 

第二节:

 

1. Hello Rails

接下来我们要输出一个所有语言的都要学习的 hello world。

要在rails应用中输出一个hello rails,你需要创建一个控制器以及一个视图。在rails的世界里,你几乎不用手动的一个个去创建各个控制器与视图,rails命令就可以帮忙我们搞定一切。

 

$ rails g controller home index

 

rails将根据命令为你创建多个文件,其他包括 app/views/home/index.html.erb, 这是一个用于显示index动作结果的模板,打开该文件,写入以下代码:

 

Html代码   收藏代码
  1. <h1>Hello, Rails</h1>  

 

 现在你已经完成了了控制器与视图。在这里,我们需要在访问 localhost:3000 的时候就显示hello rails,首先我们需要删除 public/index.html

 

$ rm public/index.html

 

然后你需要告诉rails你实际的首页在什么位置。打开 config/routes.rb, 该文件是你应用程序的路由设置,它用指定的DSL(domain-specific language)来保存条目,来告诉rails如何把进来的请求转发到相应的控制器以及动作。该文件包含了许多路由器样例,其他一个就是如何指定控制器和动作来访问根地址。

找到以 root 开头的行,改为如下:

 

Ruby代码   收藏代码
  1. Blog::Application.routes.draw do  
  2. root :to => 'home#index'  #:to指定了需要访问的控制器与动作  
  3. end  

 现在你可以在 http://localhost:3000 中看到Hello Rails了

 

 

2. 使用脚手架快速搭建网页

rails的脚手架可以快速生成应用程序的一些片段,如果你需要为一个资源创建一系列的控制器视图模型,那么脚手架就是你需要的工具

 

3. 创建资源

对于一个博客程序,你可以以生成一个Post资源脚手架开始:

 

$ rails g scaffold post name:string title:string content:text

 

该命令将为你构建15个文件:

文件 说明
db/migrate/20101122214725_create_posts.rb 创建posts表的迁移任务 (你的文件名称将包括一个不同的时间戳)
app/models/post.rb Post 模型
test/fixtures/posts.yml 用于测试的posts夹具
app/controllers/posts_controller.rb Posts控制器
app/views/posts/index.html.erb posts的首页视图
app/views/posts/edit.html.erb posts的编辑视图
app/views/posts/show.html.erb 显示单个post的视图
app/views/posts/new.html.erb 创建post的视图
app/views/posts/_form.html.erb 用于编辑与新建post的局部表单模板
app/helpers/posts_helper.rb post视图的帮助器
test/unit/post_test.rb post模型的单元测试工具
test/functional/posts_controller_test.rb posts控制器的功能测试工具
test/unit/helpers/posts_helper_test.rb posts帮助器的单元测试工具
config/routes.rb 为posts添加路由信息
public/stylesheets/scaffold.css 脚手架的默认CSS

rails generate scaffold 命令产生的其中一个文件就是数据库迁移文件,Migration 是设计用来方便创建和修改数据库的Ruby类。rails使用rake任务来运行迁移。而且 Migration 可以撤销先前应用到数据库内的迁移操作。我们现在来看一下所生成的迁移文件(记住:迁移任务的文件名永远是唯一的):

Ruby代码   收藏代码
  1. class CreatePosts < ActiveRecord::Migration  
  2.   def self.up  
  3.     create_table :posts do |t|  
  4.       t.string :name  
  5.       t.string :title  
  6.       t.text :content  
  7.   
  8.       t.timestamps  
  9.     end  
  10.   end  
  11.   
  12.   def self.down  
  13.     drop_table :posts  
  14.   end  
  15. end  

 上面的迁移创建了2个方法,up 是在你迁移进数据库时调用的,而down是你撤销迁移时用到的。up 命令创建了一个posts表以及2个string列,1个text列,同时它也为表自动创建了2个时间戳列,created_at, update_at 用于记录的创建与更新。下面我们运行数据库迁移任务:

 

$ rake db:migrate

 

rails 将执行迁移并告诉你它已经为你创建了posts表

 

4. 添加超链接

下面我们将在首页添加一个到posts的超链接:

Html代码   收藏代码
  1. <h1>Hello Rails</h1>  
  2. <p><%= link_to 'My Blog', posts_path %></p>  

 link_to 方法是rails的内置视图帮助器的功能之一,它根据参数创建一段超链接代码

 

现在你可以在浏览器内完成你博客的文章输入了。打开 http://localhost:3000 点击超链接:

Posts Index screenshot

rails将转到posts控制器的index动作,现在数据库里还没人任何记录,点击 New post 你可以转到新建post视图然后创建一条记录。之后你会发现你可以编辑,查看,以及删除该记录,而这么多功能只是用了一个 rails generate scaffold 命令来完成的。

 

注意:在development模式下(默认)rails会在每次请求时重新加载你的代码,所以你不必要在每次修改后重启你的服务器。

 

5. 模型

打开模型文件 app/models/post.rb,你会发现里面只是一个空的类:

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2. end  

 实际上远远不是如此,Post类继承了ActiveRecord::Base类,Active Record为你的rails提供了大量的功能,包括基本的数据库CRUD操作,验证操作,复杂的搜索支持以及模型之间的各种关联。

 

下面我们为post模型添加一些验证:

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.   validates :name:presence => true  
  3.   validates :title:presence => true:length => {:minimum => 5}  
  4. end  

 这些改动确保了了所有的posts都必须具有name与title属性,并且title必须包含至少5个字符。

 

要查看动作的验证,你可以使用控制台。控制台可以根据你的上下文环境执行ruby代码:

 

$ rails c (rails console)

 

控制台加载完成后, 你可以操作你的模型

 

irb(main):001:0> p = Post.new(:content => 'a new post')

=> #<Post id: nil, name: nil, title: nil, content: "a new post", created_at: nil, updated_at: nil>

irb(main):002:0> p.save

=> false

irb(main):003:0> p.errors

=> {:name=>["can't be blank"], :title=>["can't be blank", "is too short (minimum is 5 characters)"]}

irb(main):004:0>

 

该代码显示了创建一个post实例,然后试图保存但是返回了false(也就是保存失败), 然后查看下发生的错误

 

当你结束控制台,输入exit然后回车就可以了。

 

注意:与 development 服务器不同,控制台不会自动重载你的代码。所以当你的模型类发生改变时,输入 reload! 来重载代码。


第三节

1. 列出所有Posts

要开始查看功能的最简单的地方应该是列举所有记录的代码了。现在我们打开 app/controllers/post_controller.rb, 看到 index 方法

 

Ruby代码   收藏代码
  1. def index  
  2.     @posts = Post.all  
  3.   
  4.     respond_to do |format|  
  5.       format.html # index.html.erb  
  6.       format.xml  { render :xml => @posts }  
  7.     end  
  8. end  

 

 实际上在这里有2个地方与rails2不同了,第一是查询的方式,以前我们可能会这样写

Ruby代码   收藏代码
  1. @posts = Post.find(:all)  

,那么现在这种方式完全改变了,现在多出了一个重要的类 ActivaRecord::Relation 在里面分装了大量的数据库相关的操作。第二呢,我们不用写成上面的这种代码了:

Ruby代码   收藏代码
  1. respond_to :html,:js,:xml  
  2. def index  
  3.     @posts = Post.all  
  4.     respond_with @posts  
  5. end  

 这样很容易解释,相应的格式请求使用相应的模板,比如我们访问 http://localhost:3000/posts.xml 那么我们会得到一个xml格式的posts, 是不是很REST? 这里我们加了一个:js, 这为后面教程中使用ujs做下铺垫,那么具体的用法我们会在后面教程中给大家一一说明。

 

Rails把所有该动作中的实例变量传递给相应的视图,这里是 app/views/posts/index.html.erb :

Html代码   收藏代码
  1. <h1>Listing posts</h1>  
  2.    
  3. <table>  
  4.   <tr>  
  5.     <th>Name</th>  
  6.     <th>Title</th>  
  7.     <th>Content</th>  
  8.     <th></th>  
  9.     <th></th>  
  10.     <th></th>  
  11.   </tr>  
  12.    
  13. <% @posts.each do |post| %>  
  14.   <tr>  
  15.     <td><%= post.name %></td>  
  16.     <td><%= post.title %></td>  
  17.     <td><%= post.content %></td>  
  18.     <td><%= link_to 'Show', post %></td>  
  19.     <td><%= link_to 'Edit', edit_post_path(post) %></td>  
  20.     <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>  
  21.   </tr>  
  22. <% end %>  
  23. </table>  
  24.    
  25. <br />  
  26.    
  27. <%= link_to 'New post', new_post_path %>  
 

该视图遍历@posts数组来显示内容以及链接,一些要注意的地方:

  • link_to 绑定了特定的记录操作
  • edit_post_path 与 new_post_path 实际上在你设定 routes.rb 中相关resources的时候就已经给你生成的帮助器(好像一共有7个),你可以在控制器中看到不同的这种帮助器。

 

这里还有一点值得注意:在先前版本的rails中你可能需要使用 <%= h post.name %> 来实现HTML转义,那么在3.0中,默认就已经是转义的了,如果你需要获得未转义的HTML,你需要这样写 <%= raw post.name %> (raw我想大家很容易理解, java中也经常有这种警告)

 

2. 自定义布局

当rails渲染视图给浏览器时,它会把视图放置到布局内然后在输出。在先前的rails版本中,rails g scaffold 命令会自动创建控制器指定的布局,比如 app/views/layouts/posts.html.erb, 那么在3.0中 app/views/layouts/application.html.erb 作用与所有控制器。打开你的编辑器,把该布局修改如下:

Html代码   收藏代码
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.   <title>Blog</title>  
  5.   <%= stylesheet_link_tag :all %>  
  6.   <%= javascript_include_tag :defaults %>  
  7.   <%= csrf_meta_tag %>  
  8. </head>  
  9. <body style="background: #EEEEEE;">  
  10.    
  11. <%= yield %>  
  12.    
  13. </body>  
  14. </html>  
 现在你刷新你的页面你会发现背景变成了灰色。

 

3. 创建新的Post

创建一个新的Post将调用两个动作,第一个是new,它将初始化一个Post对象。

Ruby代码   收藏代码
  1. def new  
  2.     @post = Post.new  
  3.     respond_with @post  
  4. end  

 (同样我们在这里改为更简单的方式, 是不是很DRY?)

在 new.html.erb 视图中显示一个空的Post给用户:

Html代码   收藏代码
  1. <h1>New post</h1>  
  2.   
  3. <%= render 'form' %>  
  4.   
  5. <%= link_to 'Back', posts_path %>  

 <%= render 'form' %> 是我们第一次介绍Rails的局部模板。局部模板实际上也是一个视图文件,只是该文件可以被多个视图重复引用。在这种情况下,form可用于创建,更新post,然后我们可以看到 app/views/posts/_form.html.erb:

Html代码   收藏代码
  1. <%= form_for(@post) do |f| %>  
  2.   <% if @post.errors.any? %>  
  3.     <div id="error_explanation">  
  4.       <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>  
  5.   
  6.       <ul>  
  7.       <% @post.errors.full_messages.each do |msg| %>  
  8.         <li><%= msg %></li>  
  9.       <% end %>  
  10.       </ul>  
  11.     </div>  
  12.   <% end %>  
  13.   
  14.   <div class="field">  
  15.     <%= f.label :name %><br />  
  16.     <%= f.text_field :name %>  
  17.   </div>  
  18.   <div class="field">  
  19.     <%= f.label :title %><br />  
  20.     <%= f.text_field :title %>  
  21.   </div>  
  22.   <div class="field">  
  23.     <%= f.label :content %><br />  
  24.     <%= f.text_area :content %>  
  25.   </div>  
  26.   <div class="actions">  
  27.     <%= f.submit %>  
  28.   </div>  
  29. <% end %>  

 局部模板可以同样接受视图的实例变量,当然也可以接受视图传递过来的变量。这里有一个另一种写法。

首先我们先稍微了解一下局部视图。

  • 局部视图都以 下划线('_')开头
  • 局部视图本事存在一个变量可以传递到局部视图里面,该变量是 去掉下划线,比如我的局部视图是 _form.erb, 那么在局部里面有存在一个 form 的变量, 当然还可以设定其他变量或者集合,这些我们将在后面的教程中详细学习。

现在,我们有一个 @post 变量, 那么我们可以把 render 改为 <%= render @post %>, rails很聪明,那么我们在局部视图中就会存在一个 post 变量,所以在局部视图中 把所有@post改为post,最后我们还要把 _form.html.erb 改为 _post.html.erb. 大家可以试一下,当然其实在本教程中完全没有必要,我这样说只是在为后面的教程做铺垫,这样大家就会更容易明白。

 

那么关于局部视图的用法我们会在后面的内容中详细的介绍。

 

我们现在再来看下 form_for, form_for 用于根据模型对象生成表单。比如 f.text_field :name 会告诉rails生成一个文本输入框,并且保存模型的name属性。(实际上生成的是 <input type='text' name='post[name]' />)

 

实际上 form_for 方法知道你是new还是edit,然后对提交按钮设置相应的value,(比如在这里new里面是create post, edit里面是update post)

 

如果你需要创建与模型无关的表单,可以使用 form_tag 方法

 

当用户点击 Create Post 按钮时,浏览器将会把表单信息提交给create方法(RAILS知道调用create方法,因为这是POST提交的):

Java代码   收藏代码
  1. def create  
  2.     @post = Post.new(params[:post])  
  3.     if @post.save  
  4.       respond_with @post, :notice => 'Post was successfully created.'  
  5.     else  
  6.       render :action => 'new'  
  7.     end  
  8. end  

 create方法根据传递的post参数初始化了一个post对象,保存成功以后放回用户请求的相应格式。然后把用户重定向到show页面并且设置一条成功创建的信息反馈给用户。

 

如果post由于验证失败等原因未能成功保存,那么控制器将把用户返回到new页面,并显示相应的错误信息。

 

“Post was successfully created.”保存在rails的flash哈希内,我们可以在视图层用<%=flash[:notice]%>查看(那么现在我们只需要 =notice 就可以了)


第四节

1. 显示一条Post

当你在index页面点击一条文章的链接时,它将指向一条类似 http://localhost:3000/posts/1 的地址。Rails是把它作为show动作资源来解释的,然后传递 1 作为 :id 的参数。下面是 show 动作:

 

Ruby代码   收藏代码
  1. def show  
  2.   @post = Post.find(params[:id])  
  3.   respond_with @post  
  4. end  

 

 show方法通过传入id值使用 Post.find 来搜索数据库中的单条记录,记录找到之后,Rails使用 show.html.erb 视图进行渲染:

 

Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18.    
  19. <%= link_to 'Edit', edit_post_path(@post) %> |  
  20. <%= link_to 'Back', posts_path %>  

 

 

2. 编辑Posts

和创建Post一样,编辑post也是一个两部分处理。第一步请求 edit_post_path(@post) , 该方法将调用控制器中的 edit 动作:

 

Ruby代码   收藏代码
  1. def edit  
  2.   @post = Post.find(params[:id])  
  3. end  

 

 找到请求的记录之后,Rails使用 edit.html.erb 视图显示出来:

 

Html代码   收藏代码
  1. <h1>Editing post</h1>  
  2.    
  3. <%= render 'form' %>  
  4.    
  5. <%= link_to 'Show', @post %> |  
  6. <%= link_to 'Back', posts_path %>  

 

 和 new 动作一样,Rails使用相同的 _form.erb 局部模板,不过这次,该表单将使用 PUT 方式到 PostsController, 而且提交按钮将显示为 “Update Post”。注意这里的 <%= link_to 'Show', @post %> 实际上是 <%= link_to 'Show', @post.id %>。

 

提交由该视图创建的表单将调用 update 动作:

 

Ruby代码   收藏代码
  1. def update  
  2.   @post = Post.find(params[:id])  
  3.   if @post.update_attributes(params[:post])  
  4.     respond_with @post:notice => 'Post was successfully updated.'   
  5.   else  
  6.     render :action => 'edit'  
  7.   end  
  8. end  

 在update方法中,首先rails使用:id参数获取数据库中相应的post记录,然后使用 update_attributes 来更新表单中的内容到数据库中。如果更新成功,转到 show 页面,如果更新失败,那么重新回到 edit 页面。

 

3. 删除一条Post

最后,点击一条post的删除链接将请求destroy动作。

Ruby代码   收藏代码
  1. def destroy  
  2.   @post = Post.find(params[:id])  
  3.   @post.destroy  
  4.   respond_with @post  
  5. end  

 destroy方法将从数据库中移除相应的记录,然后浏览器将跳转到 index 页面。


第五节

1. 添加第二个模型

在前面的教程中,我们已经学会的使用脚手架快速搭建一个简单的应用,毕竟脚手架不能做任何事情,现在我们需要在应用中添加第二个模型了。

 

模型在rails中使用单数形式,而其相关的数据库将使用复数名称。

 

那么对于一个博客来说,评论总是少不了的,我们现在就要创建一个 Comment 模型。对于大多数的 rails 程序员来说,一般都是通过rails的生成器来生成模型,在这里我们也一样:

 

$ rails g model comment commenter:string body:text post:references

 

该命令将创建4个文件:

 

  • app/models/comment.rb – 模型
  • db/migrate/20101128142329_create_comments.rb – 迁移文件
  • test/unit/comment_test.rb and test/fixtures/comments.yml – 测试文件.
首先,我们看一下 comment.rb
Ruby代码   收藏代码
  1. class Comment < ActiveRecord::Base  
  2.   belongs_to :post  
  3. end  
 初看之下,你可能会觉得它与post模型很相识,不同之处在于它多了一行 belongs_to 声明,而这将会建立它与post模型之间的关联, 那么在后面的教程中我们将详细学习。

同时,Rails还生成了迁移文件来创建相应的表:
Ruby代码   收藏代码
  1. class CreateComments < ActiveRecord::Migration  
  2.   def self.up  
  3.     create_table :comments do |t|  
  4.       t.string :commenter  
  5.       t.text :body  
  6.       t.references :post  
  7.    
  8.       t.timestamps  
  9.     end  
  10.   end  
  11.    
  12.   def self.down  
  13.     drop_table :comments  
  14.   end  
  15. end  
 与之前的rails版本不同,这里多了一个 t.references 定义。t.references :post 将建立一个posts表的外键 post_id, 值得注意的是这里的 :post 指的模型而不是表,这点一定要搞清楚。
现在运行迁移操作把:

$ rake db:migrate

2. 关联模型
Active Record的关联让你很容易的声明模型之间的关系。对于Posts和Comments, 很明显是一对多的关系。
前面rails已经为我们声明了 belongs_to 关系,现在我们需要再声明一个 has_many 关系, 打开 post.rb 然后添加下面一行:
Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.   validates :name,  :presence => true  
  3.   validates :title:presence => true,  
  4.                     :length => { :minimum => 5 }  
  5.    
  6.   <strong>has_many :comments</strong>  
  7. end  
 现在双方都已经建立起一系列的关系,打个比方,假如你有一个实例变量 @post 包含一条 post,那么你可以使用 @post.comments 来检索所有该 post 中的 comment。

3. 为comments添加路由
和home控制器一样,我们需要添加一条路由,这样rails可以知道哪里可以看到评论。再次打开 routes.rb 你可以看到上次手脚架自动给posts添加的路由条目,我们编辑下该条目:
Ruby代码   收藏代码
  1. resources :posts do  
  2.   resources :comments  
  3. end  
 这将使 comments 资源作为 posts 资源的嵌套资源。而这也反映出 post 与 comment 之间的关系。

4. 生成控制器
模型有了,现在你需要创建comments控制器了,同样我们使用生成器来创建该控制器:

$ rails g controller comments

与任何博客一样,我们的读者将在他们阅读文章后直接添加评论,一旦完成评论的添加,他们将跳转到post的show页面,所以,我们的CommnetsController仅需提供创建以及删除垃圾评论的方法即可。

首先,我们修改下post的show.html.erb模板使之可以创建与显示评论:
Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18. <h2>Add a comment:</h2>  
  19. <%= form_for([@post, @post.comments.build]) do |f| %>  
  20.   <div class="field">  
  21.     <%= f.label :commenter %><br />  
  22.     <%= f.text_field :commenter %>  
  23.   </div>  
  24.   <div class="field">  
  25.     <%= f.label :body %><br />  
  26.     <%= f.text_area :body %>  
  27.   </div>  
  28.   <div class="actions">  
  29.     <%= f.submit %>  
  30.   </div>  
  31. <% end %>  
  32.    
  33. <%= link_to 'Edit Post', edit_post_path(@post) %> |  
  34. <%= link_to 'Back to Posts', posts_path %> |  
 这将在post的show页面中显示一个comment表单来创建评论,而这将调用CommentsController中的create方法,如下:
Ruby代码   收藏代码
  1. class CommentsController < ApplicationController  
  2.   def create  
  3.     @post = Post.find(params[:post_id])  
  4.     @comment = @post.comments.create(params[:comment])  
  5.     redirect_to post_path(@post)  
  6.   end  
  7. end  
 你可能会发现这里要比之前的posts控制器中稍微复杂一些,这就是你之前设定嵌套的片效应。每一个评论的请求需要追踪该评论所附加的Post,所以find首先会找出该Post对象。

接下来,代码调用了关联中存在的方法,我们在这里使用create方法来创建并保存该条评论,这将自动关联到对应的post记录。

一旦我们完成了评论的保存,我们就要使用 post_path @post 把用户跳转到原来显示该post的show页面。正如我们所看到的,它调用了PostsController中的show方法来渲染show.html.erb 模板。这里我们需要显示所有关于该Post的评论,所以让我们更改下 show.html.erb 页面:
Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18. <h2>Comments</h2>  
  19. <% @post.comments.each do |comment| %>  
  20.   <p>  
  21.     <b>Commenter:</b>  
  22.     <%= comment.commenter %>  
  23.   </p>  
  24.    
  25.   <p>  
  26.     <b>Comment:</b>  
  27.     <%= comment.body %>  
  28.   </p>  
  29. <% end %>  
  30.    
  31. <h2>Add a comment:</h2>  
  32. <%= form_for([@post, @post.comments.build]) do |f| %>  
  33.   <div class="field">  
  34.     <%= f.label :commenter %><br />  
  35.     <%= f.text_field :commenter %>  
  36.   </div>  
  37.   <div class="field">  
  38.     <%= f.label :body %><br />  
  39.     <%= f.text_area :body %>  
  40.   </div>  
  41.   <div class="actions">  
  42.     <%= f.submit %>  
  43.   </div>  
  44. <% end %>  
  45.    
  46. <br />  
  47.    
  48. <%= link_to 'Edit Post', edit_post_path(@post) %> |  
  49. <%= link_to 'Back to Posts', posts_path %> |  
 现在你可以在你的博客中添加post和评论了,并且把它们显示在正确的地方。

第六节

那么从前面的教程中我们学习了如何创建一个简单的博客应用, 我个人觉得无论你是新手还是从rails2过来, rails3还是比较容易上手的, 现在我们就来看下rails3相比rails2, 进步在哪里, 优势又在什么地方. (本来这章打算写ujs的, 无奈工作繁忙只能推到周日了)

 

1. 脚本命令

旧的命令                                      新的用法

script/generate                          rails g

script/console                            rails c

script/server                              rails s

script/dbconsole                        rails db

 

 

2. 配置文件

rails2: config/environment.rb

 

Ruby代码   收藏代码
  1. Rails::Initializer.run do |config|  
  2.     config.load_paths += %W( #{RAILS_ROOT}/extras )  
  3.     config.gem "bj"  
  4.     config.gem "sqlite3-ruby":lib => "sqlite3"  
  5.     config.gem "aws-s3":lib => "aws/s3"  
  6.     config.plugins = [ :exception_notification ]  
  7.     config.time_zone = 'UTC'  
  8. end  

 

 

rails3:config/application.rb

 

Ruby代码   收藏代码
  1. module APP_NAME  
  2.     class Application < Rails::Application  
  3.         config.load_paths += %W( #{RAILS_ROOT}/extras )  
  4.         config.plugins = [ :exception_notification ]  
  5.         config.time_zone = 'UTC'  
  6.     end  
  7. end  

 

 这样就变成了一种架构式的应用, 我们可以根据方便的对config进行操作

 

3. 路由

在rails3中, 已经的路由可以继续工作, 而新的路由方式更加简洁.

在 rails2 中:

Ruby代码   收藏代码
  1. map.resources :posts do |post|  
  2.     post.resources :comments  
  3. end  

 而在rails3中, 表达更为形象:

Ruby代码   收藏代码
  1. resources :posts do  
  2.     resources :comments  
  3. end  

 

对于一些复杂的路由, rails2:

Ruby代码   收藏代码
  1. post.resources :comments,  
  2.                  :member => { :preview => :post },  
  3.                  :collection => { :archived => :get }  

 在rails3中可以这样表达:

Ruby代码   收藏代码
  1. resources :comments do  
  2.     member do  
  3.         post :preview  
  4.     end  
  5.     collection do  
  6.         get :archived  
  7.     end  
  8. end  

 不够简洁? 我们还可以这样做:

Ruby代码   收藏代码
  1. resources :comments do  
  2.     post :preview:on => :member  
  3.     get :archived:on => :collection  
  4. end  

 

对于基本路由, rails2:

Ruby代码   收藏代码
  1. map.connect 'login':controller => 'session':action => 'new'  

 那么在rails3中:

Ruby代码   收藏代码
  1. match 'login' => 'session#new'  

 

对于具名路由, rails2:

Ruby代码   收藏代码
  1. map.login 'login':controller => 'session':action => 'new'  

在rails3中:

Ruby代码   收藏代码
  1. match 'login' => 'session#new':as => :login  

 

对于程序根路由, rails2:

Java代码   收藏代码
  1. map.root :controller => 'users', :action => 'index'  

 rails3:

Ruby代码   收藏代码
  1. root :to => 'users#index'  

 

对于遗留路由, rails2:

Ruby代码   收藏代码
  1. map.connect ':controller/:action/:id'  
  2. map.connect ':controller/:action/:id.:format'  

 那么在rails3中写法更优雅:

Ruby代码   收藏代码
  1. match ':controller(/:action(/:id(.:format)))'  

 

对于路由参数, rals2:

Ruby代码   收藏代码
  1. map.connect '/articles/:year/:month/:day':controller => 'posts':action => 'index'  

 rails3:

Ruby代码   收藏代码
  1. match '/articles/:year/:month/:day' => "posts#index"  

 

那么对于存档请求, 比如rails2:

Ruby代码   收藏代码
  1. map.connect '/articles/:year/:month/:day':controller => 'posts':action => 'index'  
  2. map.connect '/articles/:year/:month':controller => 'posts':action => 'index'  
  3. map.connect '/articles/:year':controller => 'posts':action => 'index'  

 在rails3中:

Ruby代码   收藏代码
  1. match '/articles(/:year(/:month(/:day)))' => "posts#index"  

 

指定请求方式, rails2:

Ruby代码   收藏代码
  1. map.connect '/articles/:year':controller => 'posts':action => 'index',  
  2. :conditions => {:method => :get}  

 在rails3中:

Ruby代码   收藏代码
  1. match '/articles/:year' => "posts#index":via => :get  
  2. #或者更简单的:  
  3. get '/articles/:year' => "posts#index"  

 

对于跳转, rails3:

Ruby代码   收藏代码
  1. match 'signin':to => redirect("/login")  
  2. match 'users/:name':to => redirect {|params| "/#{params[:name]}" }  
  3. match 'google' => redirect('http://www.google.com/')  

 

路由约束: rails2中实际上使用了 :requirements 符号

Ruby代码   收藏代码
  1. map.connect '/:year':controller => 'posts':action => 'index',  
  2.                                     :requirements => { :year => /\d{4}/ }  

 在rails3中:

Ruby代码   收藏代码
  1. match '/:year' => "posts#index":constraints => {:year => /\d{4}/}  
Ruby代码   收藏代码
  1. :constraints => { :user_agent => /iphone/ }  
  2. :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }  
  3. constraints(:host => /localhost/) do  
  4.     resources :posts  
  5. end  
  6. constraints IpRestrictor do  
  7.     get 'admin/accounts' => "queenbee#accounts"  
  8. end  

 

对于Rack应用, rails3:

Ruby代码   收藏代码
  1. get 'hello' => proc { |env| [200, {}, "Hello Rack"] }  
  2.   
  3. get 'rack_endpoint' => PostsController.action(:index)  
  4.   
  5. get 'rack_app' => CustomRackApp  
 

4. Bundler与ActionController

一个典型的rails应用, 我们一般需要在 environment.rb 指定你的 gems:

Ruby代码   收藏代码
  1. config.gem "haml"  
  2. config.gem "chronic":version => '0.2.3'  

 然后我们运行 $ rake gems:install, 该命令会取得并下载然后安装编译这些gems到你的系统RubyGems目录中.

之后我们运行 $ rake gems:unpack:dependencise, 把这些gem打包到你应用程序的vendor/gems目录中去.

 

这样做产生的问题:

1. 它直接绑定到Rails中

2. 没有从本质上解决依赖问题

3. 运行时容易发生冲突

 

在rails3中, 使用了 bundle 命令:

直接在你的 gemfile 中指定你的 gem

Ruby代码   收藏代码
  1. gem "haml"  
  2. gem "chronic"'0.2.3'  

 然后运行 $ bundle, 该命令会会取得并下载然后安装编译这些gems

然后运行 $ bundle package 把gem源移到/vendor/cache中去.

 

这样rails应用中的gem与系统中的gem就不会相冲突.

 

 

一般的控制器语法:

Ruby代码   收藏代码
  1. class UsersController < ApplicationController  
  2.     def index  
  3.         @users = User.all  
  4.         respond_to do |format|  
  5.             format.html  
  6.             format.xml { render :xml => @users.to_xml }   
  7.         end  
  8.     end  
  9.   
  10.     def show  
  11.         @user = User.find(params[:id])  
  12.         respond_to do |format|  
  13.             format.html # show.html.erb  
  14.             format.xml { render :xml => @user }  
  15.         end  
  16.     end  
  17.   
  18. ...  

 

改进的语法:

Ruby代码   收藏代码
  1. class UsersController < ApplicationController  
  2.     respond_to :html:xml:json  
  3.     def index  
  4.         @users = User.all  
  5.         respond_with(@users)  
  6.     end  
  7.     def show  
  8.         @user = User.find(params[:id])  
  9.         respond_with(@user)  
  10.     end  
  11. ...  

 

 

5. ActionMailer

rails2:  $ script/generate mailer UserMailer welcome forgot_password

这将创建 app/models/user_mailer.rb

那么在rails3中: $ rails g mailer UserMailer welcome forgot_password

这将创建 app/mailers/user_mailer.rb

 

在实现部分, rails2:

Ruby代码   收藏代码
  1. def welcome(user, subdomain)  
  2.     subject 'Welcome to TestApp'  
  3.     recipients user.email  
  4.     from 'admin@testapp.com'  
  5.     body :user => user, :subdomain => subdomain  
  6. end  
Ruby代码   收藏代码
  1. UserMailer.deliver_welcome(user, subdomain)   

在rails3中:

Ruby代码   收藏代码
  1. def welcome(user, subdomain)  
  2.     @user = user  
  3.     @subdomain = subdomain  
  4.     mail(:from => "admin@testapp.com",  
  5.             :to => user.email,  
  6.             :subject => "Welcome to TestApp")  
  7. end  
Ruby代码   收藏代码
  1. UserMailer.welcome(user, subdomain).deliver  

 

相比rails2, 我们在rails3下实现一个mail要简单的多:

Ruby代码   收藏代码
  1. class UserMailer < ActionMailer::Base  
  2.     default :from => "admin@testapp.com",  
  3.                 :reply_to => "noreply@testapp.com",  
  4.                 "X-Time-Code" => Time.now.to_i.to_s  
  5.     def welcome(user, subdomain)  
  6.         @user = user  
  7.         @subdomain = subdomain  
  8.         attachments['test.pdf'] = File.read("#{Rails.root}/public/test.pdf")  
  9.         mail(:to => @user.email, :subject => "Welcome to TestApp"do |format|  
  10.             format.html { render 'other_html_welcome' }  
  11.             format.text { render 'other_text_welcome' }  
  12.         end  
  13.     end  
  14. end   

 

 

6. ActiveRelation 以及 ActiveModel

在rails2中, 我们经常使用下面的方法来进行查询:

Ruby代码   收藏代码
  1. @posts = Post.find(:all:conditions => {:published => true})  

 该方式将立即查询数据库然后返回Posts数组

 

而在rails3中:

Ruby代码   收藏代码
  1. @posts = Post.where(:published => true)  

 该方法不会查询数据库, 仅仅返回一个 ActiveRecord::Relation 对象, 然后:

Ruby代码   收藏代码
  1. @posts = Post.where(:published => true)  
  2. if params[:order]  
  3.     @posts = @posts.order(params[:order])  
  4. end  
  5. @posts.each do |p|  
  6.     ...                 #在这里进行查询, 实现延迟加载  
  7. end  

 

对于命名范围, 在rails2中:

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.     default_scope :order => 'title'  
  3.     named_scope :published:conditions => {:published => true}  
  4.     named_scope :unpublished:conditions => {:published => false}  
  5. end  

而在rails3中:

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.     default_scope order('title')  
  3.     scope :published, where(:published => true)  
  4.     scope :unpublished, where(:published => false)  
  5. end  

 

对于查找方法, rails2:

Ruby代码   收藏代码
  1. Post.find(:all:conditions => {:author => "Joe"}, :includes => :comments:order => "title":limit => 10)  

 在rails3:

Ruby代码   收藏代码
  1. Post.where(:author => "Joe").include(:comments).order(:title).limit(10).<strong><span style="font-size: medium;">all</span></strong>  

 

 

7. 跨站点脚本(XSS)

在rails2中, 一般我们输入一段文本的时候, 我们往往会这样写: <%= h @post.body %>

那么在rails3中, <%= @post.body %> 默认输出的是一段safe html, 如果想输出XSS, 可以在前面加上 raw


第七节

1. 重构

现在, 我们博客的文章和评论都可以工作了, 但是如果我们看下 app/views/posts/show.html.erb 模板, 你会发现其代码又长又笨, 还记得Rails的信条么? DRY!(不要做重复的事, Dont Repeat Yourself)

 

现在我们使用局部模板来清理下这些代码吧.

 

首先我们制作一个comment模板来显示一条文章下的所有评论. 创建 app/views/comments/_comment.erb :

Html代码   收藏代码
  1. <p>  
  2.   <b>Commenter:</b>  
  3.   <%= comment.commenter %>  
  4. </p>  
  5.    
  6. <p>  
  7.   <b>Comment:</b>  
  8.   <%= comment.body %>  
  9. </p>  

 然后修改 posts下面的show.html.erb模板:

Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18. <h2>Comments</h2>  
  19. <%= render @post.comments %>  
  20.    
  21. <h2>Add a comment:</h2>  
  22. <%= form_for([@post, @post.comments.build]) do |f| %>  
  23.   <div class="field">  
  24.     <%= f.label :commenter %><br />  
  25.     <%= f.text_field :commenter %>  
  26.   </div>  
  27.   <div class="field">  
  28.     <%= f.label :body %><br />  
  29.     <%= f.text_area :body %>  
  30.   </div>  
  31.   <div class="actions">  
  32.     <%= f.submit %>  
  33.   </div>  
  34. <% end %>  
  35.    
  36. <br />  
  37.    
  38. <%= link_to 'Edit Post', edit_post_path(@post) %> |  
  39. <%= link_to 'Back to Posts', posts_path %> |  

 这将为每条评论渲染 app/views/comments/下面的_comment.erb局部模板, 因为传入 render 方法的参数是一个集合 (<%= render @post.comments %>), rails会自动判断参数是否是集合, 如果是, 将遍历集合同时输出局部模板 (在这里, _comment.erb局部模板的变量名称为 comment, 然后comments集合的每个元素分配给该comment变量)

 

请想想 form_for [@post, @post.comments.build] 方法生成表单的action为: posts/:id/comment, 那么对于REST路由, 我们在后面将会深入了解.

 

同样, 我们把创建评论部分也移到局部模板中去, 创建文件 app/views/comments/_form.erb:

Html代码   收藏代码
  1. <%= form_for([@post, @post.comments.build]) do |f| %>  
  2.   <div class="field">  
  3.     <%= f.label :commenter %>  
  4.     <%= f.text_field :commenter %>  
  5.   </div>  
  6.   <div class="field">  
  7.     <%= f.label :body %>  
  8.     <%= f.text_area :body %>  
  9.   </div>  
  10.   <div class="actions">  
  11.     <%= f.submit %>  
  12.   </div>  
  13. <% end %>  

最后的show.html.erb为这个样子:

Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18. <h2>Comments</h2>  
  19. <%= render @post.comments %>  
  20.    
  21. <h2>Add a comment:</h2>  
  22. <%= render "comments/form" %>  
  23.    
  24. <br />  
  25.    
  26. <%= link_to 'Edit Post', edit_post_path(@post) %> |  
  27. <%= link_to 'Back to Posts', posts_path %> |  

  第二个局部模板就比较简单了, 没有变量的赋值, 仅仅引用了comments下的_form.erb模板, 通过"/", rails知道你渲染的是comments文件夹下的_form模板.

 

再次说明一点的是: @post 作用于任何该视图下的局部模板, 因为它是一个实例变量.

 

2. 删除评论

博客中另一个重要特性是可以删除垃圾评论, 要实现该功能, 我们需要在每条评论下建立一个链接, 并实现CommentController下面的destroy动作.

 

所以首先, 我们先在_comment.erb模板下添加一条删除链接:

Html代码   收藏代码
  1. <p>  
  2.   <b>Commenter:</b>  
  3.   <%= comment.commenter %>  
  4. </p>  
  5.    
  6. <p>  
  7.   <b>Comment:</b>  
  8.   <%= comment.body %>  
  9. </p>  
  10.    
  11. <p>  
  12.   <%= link_to 'Destroy Comment', [comment.post, comment],  
  13.                :confirm => 'Are you sure?',  
  14.                :method => :delete %>  
  15. </p>  

 同样也请留意下 link_to 生成的url: posts/:id/comments/:id, 以及产生一个delete请求(注意不是get, 想想如果可以通过get来删除数据是不是很糟糕? 一般来说对于查询数据我们使用get请求, 对于操作数据使用post请求, 所以对于一个搜索功能来说, 即使是表单提交, 那么我们也应该把表单的method设为get)

 

接下来我们实现CommentsController下的destroy方法(怎么会调用该方法? 记住这是一个REST)

Ruby代码   收藏代码
  1. def destroy  
  2.   @post = Post.find(params[:post_id])  
  3.   @comment = @post.comments.find(params[:id])  
  4.   @comment.destroy  
  5.   redirect_to(post_path(@post))  
  6. end  

 destroy方法首先找到该篇文章, 然后在@post.comments集合内锁定需要删除的评论, 删除之后跳转到该文章地址.

 

3. 删除关联对象

如果你删除一篇文章, 那么相关的评论也一并删除. 否则评论还将继续留在数据库中占用空间. Rails同样允许你使用依赖选项来激活. 修改Post模型:

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.   validates :name,  :presence => true  
  3.   validates :title:presence => true,  
  4.                     :length => { :minimum => 5 }  
  5.   has_many :comments:dependent => :destroy  
  6. end  

 :dependent => :destroy 告诉rails当删除一条post时, 相关的comments将调用destroy方法一并删除, 如果设置为 :delete_all 那么关联对象将被删除而不调用它们的 destroy 方法, 如果设置为 :nullify, 所有关联对象的外键将被设为 null 而不调用他们的 save 回调; 如果设置为 :restrict 那么该对象在有关联对象存在的时候无法被删除. (注意一点的是 :dependent 在使用 :through 选项时将被忽略)

 

4. 安全

现在我们要发布博客里, 可以我们发现所有人都可以添加 删除 修改文章和评论.

 

那么rails提供了一个非常简单的HTTP认证, 那么我们需要在全局控制器内实现它:

app/controllers/application_controller.rb

Ruby代码   收藏代码
  1. class ApplicationController < ActionController::Base  
  2.   protect_from_forgery  
  3.    
  4.   private  
  5.    
  6.   def authenticate  
  7.     authenticate_or_request_with_http_basic do |user_name, password|  
  8.       user_name == 'admin' && password == 'password'  
  9.     end  
  10.   end  
  11.    
  12. end  

 然后我们需要在PostsController阻止未认证用户的一些动作的访问, 那么在这里我们可以使用 before_filter 过滤器. 该方法将在每个动作执行前运行, 除非一些通过 :except 设置的方法:

Ruby代码   收藏代码
  1. class PostsController < ApplicationController  
  2.    
  3.   before_filter :authenticate:except => [:index:show]  
  4.   ...  
  5. end  
 

同样的, 我们只允许认证用户可以删除评论:

Ruby代码   收藏代码
  1. class CommentsController < ApplicationController  
  2.    
  3.   before_filter :authenticate:only => :destroy  
  4.   ...  
  5. end  

 现在如果你创建post, 你将被要求输入用户名和密码.


第八节

1. 构建多模型表单

现在的博客一般都有便签功能, 方便读者通过关键字索引文章. 要实现该特性你的应用需要在一个表单中交互多个模型. 那么这时候Rails提供了另一个重要的功能: 嵌套表单

 

为了展示, 我们将为post的多标签提供支持. 首先, 我们需要为标签创建模型:

 

$ rails g model tag name:string post:references

 

然后:

 

$ rake db:migrate

 

接下来, 编辑post.rb建立另一半关联, 并告诉rails你需要通过posts来编辑tags:

 

Ruby代码   收藏代码
  1. class Post < ActiveRecord::Base  
  2.   validates :name,  :presence => true  
  3.   validates :title:presence => true,  
  4.                     :length => { :minimum => 5 }  
  5.    
  6.   has_many :comments:dependent => :destroy  
  7.   has_many :tags #实际上对于标签来说应该是一个多对多的关联, 为了方便教程我在这里使用一对多关系  
  8.    
  9.   accepts_nested_attributes_for :tags:allow_destroy => :true,  
  10.     :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }  
  11. end  

 

 :allow_destroy 告诉rails在视图上显示一个移除复选框, 而 :reject_if 将会阻止任何空属性的标签的保存.

现在我们修改 views/posts/_form.erb 模板来加入tag:

 

Html代码   收藏代码
  1. <% @post.tags.build %>  
  2. <%= form_for(@post) do |post_form| %>  
  3.   <% if @post.errors.any? %>  
  4.     <div id="error_explanation">  
  5.       <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>  
  6.   
  7.       <ul>  
  8.       <% @post.errors.full_messages.each do |msg| %>  
  9.         <li><%= msg %></li>  
  10.       <% end %>  
  11.       </ul>  
  12.     </div>  
  13.   <% end %>  
  14.   
  15.   <div class="field">  
  16.     <%= post_form.label :name %><br />  
  17.     <%= post_form.text_field :name %>  
  18.   </div>  
  19.   <div class="field">  
  20.     <%= post_form.label :title %><br />  
  21.     <%= post_form.text_field :title %>  
  22.   </div>  
  23.   <div class="field">  
  24.     <%= post_form.label :content %><br />  
  25.     <%= post_form.text_area :content %>  
  26.   </div>  
  27.   <h2>Tags</h2>  
  28.   <%= render :partial => "tags/form", :locals => {:form => post_form} %>  
  29.   <div class="actions">  
  30.     <%= post_form.submit %>  
  31.   </div>  
  32. <% end %>  

 

 现在我们把 form_for @post do |f| 改为 |psot_form| 使之更容易理解.

 

本示例显示了 render方法的另一个参数 :locals 它可以传递局部变量, 在这里我们希望 form 变量映射为 post_form 对象.

 

同时我们还在开始处初始化一个tag对象. (试试取消该行会发生什么)

 

现在创建文件夹 views/tags/ 然后在里面添加一个 _form.erb 局部模板:

 

Html代码   收藏代码
  1. <%= form.fields_for :tags do |tag_form| %>  
  2.   <div class="field">  
  3.     <%= tag_form.label :name, 'Tag:' %>  
  4.     <%= tag_form.text_field :name %>  
  5.   </div>  
  6.   <% unless tag_form.object.nil? || tag_form.object.new_record? %>  
  7.     <div class="field">  
  8.       <%= tag_form.label :_destroy, 'Remove:' %>  
  9.       <%= tag_form.check_box :_destroy %>  
  10.     </div>  
  11.   <% end %>  
  12. <% end %>  

 

同样, 请留意下tags表单生成的html代码.

 

最后, 我们编辑show.html.erb来显示这些tags:

 

Html代码   收藏代码
  1. <p class="notice"><%= notice %></p>  
  2.    
  3. <p>  
  4.   <b>Name:</b>  
  5.   <%= @post.name %>  
  6. </p>  
  7.    
  8. <p>  
  9.   <b>Title:</b>  
  10.   <%= @post.title %>  
  11. </p>  
  12.    
  13. <p>  
  14.   <b>Content:</b>  
  15.   <%= @post.content %>  
  16. </p>  
  17.    
  18. <p>  
  19.   <b>Tags:</b>  
  20.   <%= @post.tags.map { |t| t.name }.join(", ") %>  
  21. </p>  
  22.    
  23. <h2>Comments</h2>  
  24. <%= render @post.comments %>  
  25.    
  26. <h2>Add a comment:</h2>  
  27. <%= render "comments/form" %>  
  28.    
  29.    
  30. <%= link_to 'Edit Post', edit_post_path(@post) %> |  
  31. <%= link_to 'Back to Posts', posts_path %> |  

 

 现在你可以直接在创建或编辑post页面中添加或删除tags了.

 

不过 @post.tags.map { |t| t.name }.join(", ") 感觉不是那么友好, 我们可以通过帮助器来改变.

 

2. 视图帮助器

视图帮助器位于 app/helpers, 可以提供一些可重用的视图代码片段. 在这里, 我们需要把所有tags的名称连起来并用逗号隔开, 由于它位于Posts视图, 那么我们可以在 PostsHelper 中实现:

 

Ruby代码   收藏代码
  1. module PostsHelper  
  2.   def join_tags(post)  
  3.     post.tags.map { |t| t.name }.join(", ")  
  4.   end  
  5. end  

 

 然后把上面的 <%= @post.tags.map { |t| t.name }.join(", ") %> 改为:

<%= join_tags @post %>

 

第九节

通过前面8篇文章的学习, 我们已经基本掌握了如何快速开发一个简单的博客应用程序, 本章作为入门的完结篇, 我们将要学习来为博客加上ujs, 让我们的博客加上一点ajax效果.

 

ujs全称Unobtrusive Javascript, 这也是Rails3的重要特性之一. Rails3的ujs利用了HTML5中 data-*@ 属性的巨大优势, 而摒弃了以前基于Prototype JS的构造器. (在rails1和rails2中, 我们使用ajax可能会利用 xxx.rjs 或者 xxx.js.builder, 在里面可以使用封装prototype的Page对象)

 

1. 让Rails3使用jQuery

轻量级js框架jquery相信大家一定不会陌生, 本教程中的ujs教程也是基于jquery来写的(当然你也可以使用prototype).

首先, 下载最新的jquery然后放入 public/javascripts 文件夹下面:

 

curl -L http://code.jquery.com/jquery-1.4.3.min.js > public/javascripts/jquery.js

 

同时我们也需要把原来的prototype ujs的rails.js 改成 jquery-ujs的rails.js:

 

$ curl -L http://github.com/rails/jquery-ujs/raw/master/src/rails.js > public/javascripts/rails.js

 

下载后可以看下javascripts文件夹, 里面有application, controls, dragdrop, effects, jquery, prototype 以及 rails 等js文件, 出来jquery以外, 其他都是生产bolg应用时rails加进去的.

 

现在打开 config/application.rb 文件, 修改下面的配置:

 

 

Ruby代码   收藏代码
  1. # JavaScript files you want as :defaults (application.js is always included).  
  2.     config.action_view.javascript_expansions[:defaults] = %w(jquery rails)  

 

上面的代码告诉rails, :default 符号的定义现在改成 jquery和rails啦, 启动我们的博客程序, 查看源代码中的<head>中的<script>是不是这3个js脚本.

 

2. 给评论加上ujs

当用户发表评论后, 浏览器往往会刷新页面, 那么使用ajax方式提交可以实现局部刷新, 从而避免数据交互开销以及增加客户体验.

 

现在我们修改 views/comments/_form.erb:

 

Html代码   收藏代码
  1. <%= form_for([@post, @post.comments.build], :remote=>true) do |f| %>  
  2. ...  

 

 :remote=>true告诉rails, 该表单将使用xmlhttprequest方式提交数据.

 

接着, 我们修改 CommentsController下面的create动作:

 

Ruby代码   收藏代码
  1. class CommentsController < ApplicationController  
  2.   before_filter :authenticate:only => :destroy  
  3.   respond_to :html:js:xml  
  4.     
  5.   def create  
  6.     @post = Post.find(params[:post_id])  
  7.     @comment = @post.comments.build(params[:comment])  
  8.     respond_with @comment if @comment.save  
  9.   end  
  10.   
  11.   ...  
  12. end  

 

还记得先前教程中的 respond_to 声明吗? 如果评论保存成功, rails将寻找相应的模板, 如果存在 create.html.erb 将渲染html, 如果存在 create.js.erb 当渲染js.

 

那么现在我们可以在 views/comments/下面创建 create.js.erb 模板了:

 

Js代码   收藏代码
  1. $('#comments').append("<%= escape_javascript(render @comment) %>"); // 插入 _comment 局部模板  
  2. $('#comment_body').val(""); // 清空表单  

 

 很熟悉? 是的, 完全就是jquery的写法, 只不过我们使用了一个 escape_javascript 来生成一段无<script>标记的代码.

 

现在, 我们需要修改下 Posts/ 下面的show.html.erb 模板:

 

Html代码   收藏代码
  1. <h2>Comments</h2>  
  2. <div id="comments">  
  3.     <%= render @post.comments %>  
  4. </div>  

 

 

好了, 现在试试吧.

 

3. 删除文章和评论

最后我们试着把 删除post以及评论 也加上ujs效果:

posts/index.html.erb:

 

Html代码   收藏代码
  1. <% @posts.each do |post| %>  
  2.   <tr id='<%= "post_#{post.id}" %>'>  
  3.     <td><%= post.name %></td>  
  4.     <td><%= post.title %></td>  
  5.     <td><%= post.content %></td>  
  6.     <td><%= link_to 'Show', post %></td>  
  7.     <td><%= link_to 'Edit', edit_post_path(post) %></td>  
  8.     <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete, :remote => true %></td>  
  9.   </tr>  
  10. <% end %>  

PostsController#destroy:

   def destroy

Ruby代码   收藏代码
  1. @post = Post.find(params[:id])  
  2. @post.destroy  
  3. respond_with(@post)  
 

 添加 views/posts/destroy.js.erb:

 

Js代码   收藏代码
  1. $('#post_<%= @post.id %>').hide(); //隐藏删除的记录.  

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值