更多内容 rubyonrails.org: 更多内容

Rails 入门

本文介绍如何开始使用 Ruby on Rails。

读完本文,你将学到:

1 前提条件

本文针对想从零开始开发 Rails 程序的初学者,不需要预先具备任何的 Rails 使用经验。不过,为了能顺利阅读,还是需要事先安装好一些软件:

Rails 是使用 Ruby 语言开发的网页程序框架。如果之前没接触过 Ruby,学习 Rails 可要深下一番功夫。网上有很多资源可以学习 Ruby:

记住,某些资源虽然很好,但是针对 Ruby 1.8,甚至 1.6 编写的,所以没有介绍一些 Rails 日常开发会用到的句法。

2 Rails 是什么?

Rails 是使用 Ruby 语言编写的网页程序开发框架,目的是为开发者提供常用组件,简化网页程序的开发。只需编写较少的代码,就能实现其他编程语言或框架难以企及的功能。经验丰富的 Rails 程序员会发现,Rails 让程序开发变得更有乐趣。

Rails 有自己的一套规则,认为问题总有最好的解决方法,而且建议使用最好的方法,有些情况下甚至不推荐使用其他替代方案。学会如何按照 Rails 的思维开发,能极大提高开发效率。如果坚持在 Rails 开发中使用其他语言中的旧思想,尝试使用别处学来的编程模式,开发过程就不那么有趣了。

Rails 哲学包含两大指导思想:

  • 不要自我重复(DRY): DRY 是软件开发中的一个原则,“系统中的每个功能都要具有单一、准确、可信的实现。”。不重复表述同一件事,写出的代码才能更易维护,更具扩展性,也更不容易出问题。
  • 多约定,少配置: Rails 为网页程序的大多数需求都提供了最好的解决方法,而且默认使用这些约定,不用在长长的配置文件中设置每个细节。

3 新建 Rails 程序

阅读本文时,最佳方式是跟着一步一步操作,如果错过某段代码或某个步骤,程序就可能出错,所以请一步一步跟着做。

本文会新建一个名为 blog 的 Rails 程序,这是一个非常简单的博客。在开始开发程序之前,要确保已经安装了 Rails。

文中的示例代码使用 $ 表示命令行提示符,你的提示符可能修改过,所以会不一样。在 Windows 中,提示符可能是 c:\source_code>

3.1 安装 Rails

打开命令行:在 Mac OS X 中打开 Terminal.app,在 Windows 中选择“运行”,然后输入“cmd.exe”。下文中所有以 $ 开头的代码,都要在命令行中运行。先确认是否安装了 Ruby 最新版:

有很多工具可以帮助你快速在系统中安装 Ruby 和 Ruby on Rails。Windows 用户可以使用 Rails Installer,Mac OS X 用户可以使用 Tokaido

$ ruby -v
ruby 2.1.2p95

如果你还没安装 Ruby,请访问 ruby-lang.org,找到针对所用系统的安装方法。

很多类 Unix 系统都自带了版本尚新的 SQLite3。Windows 等其他操作系统的用户可以在 SQLite3 的网站上找到安装说明。然后,确认是否在 PATH 中:

$ sqlite3 --version

命令行应该回显版本才对。

安装 Rails,请使用 RubyGems 提供的 gem install 命令:

$ gem install rails

要检查所有软件是否都正确安装了,可以执行下面的命令:

$ rails --version

如果显示的结果类似“Rails 4.2.0”,那么就可以继续往下读了。

3.2 创建 Blog 程序

Rails 提供了多个被称为“生成器”的脚本,可以简化开发,生成某项操作需要的所有文件。其中一个是新程序生成器,生成一个 Rails 程序骨架,不用自己一个一个新建文件。

打开终端,进入有写权限的文件夹,执行以下命令生成一个新程序:

$ rails new blog

这个命令会在文件夹 blog 中新建一个 Rails 程序,然后执行 bundle install 命令安装 Gemfile 中列出的 gem。

执行 rails new -h 可以查看新程序生成器的所有命令行选项。

生成 blog 程序后,进入该文件夹:

$ cd blog

blog 文件夹中有很多自动生成的文件和文件夹,组成一个 Rails 程序。本文大部分时间都花在 app 文件夹上。下面简单介绍默认生成的文件和文件夹的作用:

文件/文件夹 作用
app/ 存放程序的控制器、模型、视图、帮助方法、邮件和静态资源文件。本文主要关注的是这个文件夹。
bin/ 存放运行程序的 rails 脚本,以及其他用来部署或运行程序的脚本。
config/ 设置程序的路由,数据库等。详情参阅“设置 Rails 程序”一文。
config.ru 基于 Rack 服务器的程序设置,用来启动程序。
db/ 存放当前数据库的模式,以及数据库迁移文件。
Gemfile, Gemfile.lock 这两个文件用来指定程序所需的 gem 依赖件,用于 Bundler gem。关于 Bundler 的详细介绍,请访问 Bundler 官网
lib/ 程序的扩展模块。
log/ 程序的日志文件。
public/ 唯一对外开放的文件夹,存放静态文件和编译后的资源文件。
Rakefile 保存并加载可在命令行中执行的任务。任务在 Rails 的各组件中定义。如果想添加自己的任务,不要修改这个文件,把任务保存在 lib/tasks 文件夹中。
README.rdoc 程序的简单说明。你应该修改这个文件,告诉其他人这个程序的作用,如何安装等。
test/ 单元测试,固件等测试用文件。详情参阅“测试 Rails 程序”一文。
tmp/ 临时文件,例如缓存,PID,会话文件。
vendor/ 存放第三方代码。经常用来放第三方 gem。

4 Hello, Rails!

首先,我们来添加一些文字,在页面中显示。为了能访问网页,要启动程序服务器。

4.1 启动服务器

现在,新建的 Rails 程序已经可以正常运行。要访问网站,需要在开发电脑上启动服务器。请在 blog 文件夹中执行下面的命令:

$ rails server

把 CoffeeScript 编译成 JavaScript 需要 JavaScript 运行时,如果没有运行时,会报错,提示没有 execjs。Mac OS X 和 Windows 一般都提供了 JavaScript 运行时。Rails 生成的 Gemfile 中,安装 therubyracer gem 的代码被注释掉了,如果需要使用这个 gem,请把前面的注释去掉。在 JRuby 中推荐使用 therubyracer。在 JRuby 中生成的 Gemfile 已经包含了这个 gem。所有支持的运行时参见 ExecJS

上述命令会启动 WEBrick,这是 Ruby 内置的服务器。要查看程序,请打开一个浏览器窗口,访问 http://localhost:3000。应该会看到默认的 Rails 信息页面:

欢迎使用页面

要想停止服务器,请在命令行中按 Ctrl+C 键。服务器成功停止后回重新看到命令行提示符。在大多数类 Unix 系统中,包括 Mac OS X,命令行提示符是 $ 符号。在开发模式中,一般情况下无需重启服务器,修改文件后,服务器会自动重新加载。

“欢迎使用”页面是新建 Rails 程序后的“冒烟测试”:确保程序设置正确,能顺利运行。你可以点击“About your application's environment”链接查看程序所处环境的信息。

4.2 显示“Hello, Rails!”

要在 Rails 中显示“Hello, Rails!”,需要新建一个控制器和视图。

控制器用来接受向程序发起的请求。路由决定哪个控制器会接受到这个请求。一般情况下,每个控制器都有多个路由,对应不同的动作。动作用来提供视图中需要的数据。

视图的作用是,以人类能看懂的格式显示数据。有一点要特别注意,数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来。默认情况下,视图使用 eRuby(嵌入式 Ruby)语言编写,经由 Rails 解析后,再发送给用户。

控制器可用控制器生成器创建,你要告诉生成器,我想要个名为“welcome”的控制器和一个名为“index”的动作,如下所示:

$ rails generate controller welcome index

运行上述命令后,Rails 会生成很多文件,以及一个路由。

create  app/controllers/welcome_controller.rb
 route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  test_unit
create    test/controllers/welcome_controller_test.rb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.js.coffee
invoke    scss
create      app/assets/stylesheets/welcome.css.scss

在这些文件中,最重要的当然是控制器,位于 app/controllers/welcome_controller.rb,以及视图,位于 app/views/welcome/index.html.erb

使用文本编辑器打开 app/views/welcome/index.html.erb 文件,删除全部内容,写入下面这行代码:

<h1>Hello, Rails!</h1>

4.3 设置程序的首页

我们已经创建了控制器和视图,现在要告诉 Rails 在哪个地址上显示“Hello, Rails!”。这里,我们希望访问根地址 http://localhost:3000 时显示。但是现在显示的还是欢迎页面。

我们要告诉 Rails 真正的首页是什么。

在编辑器中打开 config/routes.rb 文件。

Rails.application.routes.draw do
  get 'welcome/index'

  # The priority is based upon order of creation:
  # first created -> highest priority.
  #
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  #
  # ...

这是程序的路由文件,使用特殊的 DSL(domain-specific language,领域专属语言)编写,告知 Rails 请求应该发往哪个控制器和动作。文件中有很多注释,举例说明如何定义路由。其中有一行说明了如何指定控制器和动作设置网站的根路由。找到以 root 开头的代码行,去掉注释,变成这样:

root 'welcome#index'

root 'welcome#index' 告知 Rails,访问程序的根路径时,交给 welcome 控制器中的 index 动作处理。get 'welcome/index' 告知 Rails,访问 http://localhost:3000/welcome/index 时,交给 welcome 控制器中的 index 动作处理。get 'welcome/index' 是运行 rails generate controller welcome index 时生成的。

如果生成控制器时停止了服务器,请再次启动(rails server),然后在浏览器中访问 http://localhost:3000。你会看到之前写入 app/views/welcome/index.html.erb 文件的“Hello, Rails!”,说明新定义的路由把根目录交给 WelcomeControllerindex 动作处理了,而且也正确的渲染了视图。

关于路由的详细介绍,请阅读“Rails 路由全解”一文。

5 开始使用

前文已经介绍如何创建控制器、动作和视图,下面我们来创建一些更实质的功能。

在博客程序中,我们要创建一个新“资源”。资源是指一系列类似的对象,比如文章,人和动物。

资源可以被创建、读取、更新和删除,这些操作简称 CRUD。

Rails 提供了一个 resources 方法,可以声明一个符合 REST 架构的资源。创建文章资源后,config/routes.rb 文件的内容如下:

Rails.application.routes.draw do

  resources :articles

  root 'welcome#index'
end

执行 rake routes 任务,会看到定义了所有标准的 REST 动作。输出结果中各列的意义稍后会说明,现在只要留意 article 的单复数形式,这在 Rails 中有特殊的含义。

$ bin/rake routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
 new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

下一节,我们会加入新建文章和查看文章的功能。这两个操作分别对应于 CRUD 的 C 和 R,即创建和读取。新建文章的表单如下所示:

新建文章表单

表单看起来很简陋,不过没关系,后文会加入更多的样式。

5.1 挖地基

首先,程序中要有个页面用来新建文章。一个比较好的选择是 /articles/new。这个路由前面已经定义了,可以访问。打开 http://localhost:3000/articles/new ,会看到如下的路由错误:

路由错误,常量 ArticlesController 未初始化

产生这个错误的原因是,没有定义用来处理该请求的控制器。解决这个问题的方法很简单,执行下面的命令创建名为 ArticlesController 的控制器即可:

$ bin/rails g controller articles

打开刚生成的 app/controllers/articles_controller.rb 文件,会看到一个几乎没什么内容的控制器:

class ArticlesController < ApplicationController
end

控制器就是一个类,继承自 ApplicationController。在这个类中定义的方法就是控制器的动作。动作的作用是处理文章的 CRUD 操作。

在 Ruby 中,方法分为 publicprivateprotected 三种,只有 public 方法才能作为控制器的动作。详情参阅 Programming Ruby 一书。

现在刷新 http://localhost:3000/articles/new,会看到一个新错误:

ArticlesController 控制器不知如何处理 new 动作

这个错误的意思是,在刚生成的 ArticlesController 控制器中找不到 new 动作。因为在生成控制器时,除非指定要哪些动作,否则不会生成,控制器是空的。

手动创建动作只需在控制器中定义一个新方法。打开 app/controllers/articles_controller.rb 文件,在 ArticlesController 类中,定义 new 方法,如下所示:

class ArticlesController < ApplicationController
  def new
  end
end

ArticlesController 中定义 new 方法后,再刷新 http://localhost:3000/articles/new,看到的还是个错误:

找不到 articles/new 所用模板

产生这个错误的原因是,Rails 希望这样的常规动作有对应的视图,用来显示内容。没有视图可用,Rails 就报错了。

在上图中,最后一行被截断了,我们来看一下完整的信息:

Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"

这行信息还挺长,我们来看一下到底是什么意思。

第一部分说明找不到哪个模板,这里,丢失的是 articles/new 模板。Rails 首先会寻找这个模板,如果找不到,再找名为 application/new 的模板。之所以这么找,是因为 ArticlesController 继承自 ApplicationController

后面一部分是个 Hash。:locale 表示要找哪国语言模板,默认是英语("en")。:format 表示响应使用的模板格式,默认为 :html,所以 Rails 要寻找一个 HTML 模板。:handlers 表示用来处理模板的程序,HTML 模板一般使用 :erb,XML 模板使用 :builder:coffee 用来把 CoffeeScript 转换成 JavaScript。

最后一部分说明 Rails 在哪里寻找模板。在这个简单的程序里,模板都存放在一个地方,复杂的程序可能存放在多个位置。

让这个程序正常运行,最简单的一种模板是 app/views/articles/new.html.erb。模板文件的扩展名是关键所在:第一个扩展名是模板的类型,第二个扩展名是模板的处理程序。Rails 会尝试在 app/views 文件夹中寻找名为 articles/new 的模板。这个模板的类型只能是 html,处理程序可以是 erbbuildercoffee。因为我们要编写一个 HTML 表单,所以使用 erb。所以这个模板文件应该命名为 articles/new.html.erb,还要放在 app/views 文件夹中。

新建文件 app/views/articles/new.html.erb,写入如下代码:

<h1>New Article</h1>

再次刷新 http://localhost:3000/articles/new,可以看到页面中显示了一个标头。现在路由、控制器、动作和视图都能正常运行了。接下来要编写新建文章的表单了。

5.2 首个表单

要在模板中编写表单,可以使用“表单构造器”。Rails 中常用的表单构造器是 form_for。在 app/views/articles/new.html.erb 文件中加入以下代码:

<%= form_for :article do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

现在刷新页面,会看到上述代码生成的表单。在 Rails 中编写表单就是这么简单!

调用 form_for 方法时,要指定一个对象。在上面的表单中,指定的是 :article。这个对象告诉 form_for,这个表单是用来处理哪个资源的。在 form_for 方法的块中,FormBuilder 对象(用 f 表示)创建了两个标签和两个文本字段,一个用于文章标题,一个用于文章内容。最后,在 f 对象上调用 submit 方法,创建一个提交按钮。

不过这个表单还有个问题。如果查看这个页面的源码,会发现表单 action 属性的值是 /articles/new。这就是问题所在,因为其指向的地址就是现在这个页面,而这个页面是用来显示新建文章表单的。

要想转到其他地址,就要使用其他的地址。这个问题可使用 form_for 方法的 :url 选项解决。在 Rails 中,用来处理新建资源表单提交数据的动作是 create,所以表单应该转向这个动作。

修改 app/views/articles/new.html.erb 文件中的 form_for,改成这样:

<%= form_for :article, url: articles_path do |f| %>

这里,我们把 :url 选项的值设为 articles_path 帮助方法。要想知道这个方法有什么作用,我们要回过头再看一下 rake routes 的输出:

$ bin/rake routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
 new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

articles_path 帮助方法告诉 Rails,对应的地址是 /articles,默认情况下,这个表单会向这个路由发起 POST 请求。这个路由对应于 ArticlesController 控制器的 create 动作。

表单写好了,路由也定义了,现在可以填写表单,然后点击提交按钮新建文章了。请实际操作一下。提交表单后,会看到一个熟悉的错误:

ArticlesController 控制器不知如何处理 create 动作

解决这个错误,要在 ArticlesController 控制器中定义 create 动作。

5.3 创建文章

要解决前一节出现的错误,可以在 ArticlesController 类中定义 create 方法。在 app/controllers/articles_controller.rb 文件中 new 方法后面添加以下代码:

class ArticlesController < ApplicationController
  def new
  end

  def create
  end
end

然后再次提交表单,会看到另一个熟悉的错误:找不到模板。现在暂且不管这个错误。create 动作的作用是把新文章保存到数据库中。

提交表单后,其中的字段以参数的形式传递给 Rails。这些参数可以在控制器的动作中使用,完成指定的操作。要想查看这些参数的内容,可以把 create 动作改成:

def create
  render plain: params[:article].inspect
end

render 方法接受一个简单的 Hash 为参数,这个 Hash 的键是 plain,对应的值为 params[:article].inspectparams 方法表示通过表单提交的参数,返回 ActiveSupport::HashWithIndifferentAccess 对象,可以使用字符串或者 Symbol 获取键对应的值。现在,我们只关注通过表单提交的参数。

如果现在再次提交表单,不会再看到找不到模板错误,而是会看到类似下面的文字:

{"title"=>"First article!", "text"=>"This is my first article."}

create 动作把表单提交的参数显示出来了。不过这么做没什么用,看到了参数又怎样,什么都没发生。

5.4 创建 Article 模型

在 Rails 中,模型的名字使用单数,对应的数据表名使用复数。Rails 提供了一个生成器用来创建模型,大多数 Rails 开发者创建模型时都会使用。创建模型,请在终端里执行下面的命令:

$ bin/rails generate model Article title:string text:text

这个命令告知 Rails,我们要创建 Article 模型,以及一个字符串属性 title 和文本属性 text。这两个属性会自动添加到 articles 数据表中,映射到 Article 模型。

执行这个命令后,Rails 会生成一堆文件。现在我们只关注 app/models/article.rbdb/migrate/20140120191729_create_articles.rb(你得到的文件名可能有点不一样)这两个文件。后者用来创建数据库结构,下一节会详细说明。

Active Record 很智能,能自动把数据表中的字段映射到模型的属性上。所以无需在 Rails 的模型中声明属性,因为 Active Record 会自动映射。

5.5 运行迁移

如前文所述,rails generate model 命令会在 db/migrate 文件夹中生成一个数据库迁移文件。迁移是一个 Ruby 类,能简化创建和修改数据库结构的操作。Rails 使用 rake 任务运行迁移,修改数据库结构后还能撤销操作。迁移的文件名中有个时间戳,这样能保证迁移按照创建的时间顺序运行。

db/migrate/20140120191729_create_articles.rb(还记得吗,你的迁移文件名可能有点不一样)文件的内容如下所示:

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

在这个迁移中定义了一个名为 change 的方法,在运行迁移时执行。change 方法中定义的操作都是可逆的,Rails 知道如何撤销这次迁移操作。运行迁移后,会创建 articles 表,以及一个字符串字段和文本字段。同时还会创建两个时间戳字段,用来跟踪记录的创建时间和更新时间。

关于迁移的详细说明,请参阅“Active Record 数据库迁移”一文。

然后,使用 rake 命令运行迁移:

$ bin/rake db:migrate

Rails 会执行迁移操作,告诉你创建了 articles 表。

==  CreateArticles: migrating ==================================================
-- create_table(:articles)
   -> 0.0019s
==  CreateArticles: migrated (0.0020s) =========================================

因为默认情况下,程序运行在开发环境中,所以相关的操作应用于 config/database.yml 文件中 development 区域设置的数据库上。如果想在其他环境中运行迁移,必须在命令中指明:rake db:migrate RAILS_ENV=production

5.6 在控制器中保存数据

再回到 ArticlesController 控制器,我们要修改 create 动作,使用 Article 模型把数据保存到数据库中。打开 app/controllers/articles_controller.rb 文件,把 create 动作修改成这样:

def create
  @article = Article.new(params[:article])

  @article.save
  redirect_to @article
end

在 Rails 中,每个模型可以使用各自的属性初始化,自动映射到数据库字段上。create 动作中的第一行就是这个目的(还记得吗,params[:article] 就是我们要获取的属性)。@article.save 的作用是把模型保存到数据库中。保存完后转向 show 动作。稍后再编写 show 动作。

后文会看到,@article.save 返回一个布尔值,表示保存是否成功。

再次访问 http://localhost:3000/articles/new,填写表单,还差一步就能创建文章了,会看到一个错误页面:

新建文章时禁止使用属性

Rails 提供了很多安全防范措施保证程序的安全,你所看到的错误就是因为违反了其中一个措施。这个防范措施叫做“健壮参数”,我们要明确地告知 Rails 哪些参数可在控制器中使用。这里,我们想使用 titletext 参数。请把 create 动作修改成:

def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

看到 permit 方法了吗?这个方法允许在动作中使用 titletext 属性。

注意,article_params 是私有方法。这种用法可以防止攻击者把修改后的属性传递给模型。关于健壮参数的更多介绍,请阅读这篇文章

5.7 显示文章

现在再次提交表单,Rails 会提示找不到 show 动作。这个提示没多大用,我们还是先添加 show 动作吧。

我们在 rake routes 的输出中看到,show 动作的路由是:

article GET    /articles/:id(.:format)      articles#show

:id 的意思是,路由期望接收一个名为 id 的参数,在这个例子中,就是文章的 ID。

和前面一样,我们要在 app/controllers/articles_controller.rb 文件中添加 show 动作,以及相应的视图文件。

def show
  @article = Article.find(params[:id])
end

有几点要注意。我们调用 Article.find 方法查找想查看的文章,传入的参数 params[:id] 会从请求中获取 :id 参数。我们还把文章对象存储在一个实例变量中(以 @ 开头的变量),只有这样,变量才能在视图中使用。

然后,新建 app/views/articles/show.html.erb 文件,写入下面的代码:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

做了以上修改后,就能真正的新建文章了。访问 http://localhost:3000/articles/new,自己试试。

显示文章

5.8 列出所有文章

我们还要列出所有文章,对应的路由是:

articles GET    /articles(.:format)          articles#index

app/controllers/articles_controller.rb 文件中,为 ArticlesController 控制器添加 index 动作:

def index
  @articles = Article.all
end

然后编写这个动作的视图,保存为 app/views/articles/index.html.erb

<h1>Listing articles</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
    </tr>
  <% end %>
</table>

现在访问 http://localhost:3000/articles,会看到已经发布的文章列表。

5.9 添加链接

至此,我们可以新建、显示、列出文章了。下面我们添加一些链接,指向这些页面。

打开 app/views/welcome/index.html.erb 文件,改成这样:

<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>

link_to 是 Rails 内置的视图帮助方法之一,根据提供的文本和地址创建超链接。这上面这段代码中,地址是文章列表页面。

接下来添加到其他页面的链接。先在 app/views/articles/index.html.erb 中添加“New Article”链接,放在 <table> 标签之前:

<%= link_to 'New article', new_article_path %>

点击这个链接后,会转向新建文章的表单页面。

然后在 app/views/articles/new.html.erb 中添加一个链接,位于表单下面,返回到 index 动作:

<%= form_for :article do |f| %>
  ...
<% end %>

<%= link_to 'Back', articles_path %>

最后,在 app/views/articles/show.html.erb 模板中添加一个链接,返回 index 动作,这样用户查看某篇文章后就可以返回文章列表页面了:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<%= link_to 'Back', articles_path %>

如果要链接到同一个控制器中的动作,不用指定 :controller 选项,因为默认情况下使用的就是当前控制器。

在开发模式下(默认),每次请求 Rails 都会重新加载程序,因此修改之后无需重启服务器。

5.10 添加数据验证

模型文件,比如 app/models/article.rb,可以简单到只有这两行代码:

class Article < ActiveRecord::Base
end

文件中没有多少代码,不过请注意,Article 类继承自 ActiveRecord::Base。Active Record 提供了很多功能,包括:基本的数据库 CRUD 操作,数据验证,复杂的搜索功能,以及多个模型之间的关联。

Rails 为模型提供了很多方法,用来验证传入的数据。打开 app/models/article.rb 文件,修改成:

class Article < ActiveRecord::Base
  validates :title, presence: true,
                    length: { minimum: 5 }
end

添加的这段代码可以确保每篇文章都有一个标题,而且至少有五个字符。在模型中可以验证数据是否满足多种条件,包括:字段是否存在、是否唯一,数据类型,以及关联对象是否存在。“Active Record 数据验证”一文会详细介绍数据验证。

添加数据验证后,如果把不满足验证条件的文章传递给 @article.save,会返回 false。打开 app/controllers/articles_controller.rb 文件,会发现,我们还没在 create 动作中检查 @article.save 的返回结果。如果保存失败,应该再次显示表单。为了实现这种功能,请打开 app/controllers/articles_controller.rb 文件,把 newcreate 动作改成:

def new
  @article = Article.new
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

new 动作中添加了一个实例变量 @article。稍后你会知道为什么要这么做。

注意,在 create 动作中,如果保存失败,调用的是 render 方法而不是 redirect_to 方法。用 render 方法才能在保存失败后把 @article 对象传给 new 动作的视图。渲染操作和表单提交在同一次请求中完成;而 redirect_to 会让浏览器发起一次新请求。

刷新 http://localhost:3000/articles/new,提交一个没有标题的文章,Rails 会退回这个页面,但这种处理方法没多少用,你要告诉用户哪儿出错了。为了实现这种功能,请在 app/views/articles/new.html.erb 文件中检测错误消息:

<%= form_for :article, url: articles_path do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>

我们添加了很多代码,使用 @article.errors.any? 检查是否有错误,如果有错误,使用 @article.errors.full_messages 显示错误。

pluralize 是 Rails 提供的帮助方法,接受一个数字和字符串作为参数。如果数字比 1 大,字符串会被转换成复数形式。

new 动作中加入 @article = Article.new 的原因是,如果不这么做,在视图中 @article 的值就是 nil,调用 @article.errors.any? 时会发生错误。

Rails 会自动把出错的表单字段包含在一个 div 中,并为其添加了一个 class:field_with_errors。我们可以定义一些样式,凸显出错的字段。

再次访问 http://localhost:3000/articles/new,尝试发布一篇没有标题的文章,会看到一个很有用的错误提示。

出错的表单

5.11 更新文章

我们已经说明了 CRUD 中的 CR 两种操作。下面进入 U 部分,更新文章。

首先,要在 ArticlesController 中添加 edit 动作:

def edit
  @article = Article.find(params[:id])
end

视图中要添加一个类似新建文章的表单。新建 app/views/articles/edit.html.erb 文件,写入下面的代码:

<h1>Editing article</h1>

<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>

这里的表单指向 update 动作,现在还没定义,稍后会添加。

method: :patch 选项告诉 Rails,提交这个表单时使用 PATCH 方法发送请求。根据 REST 架构,更新资源时要使用 HTTP PATCH 方法。

form_for 的第一个参数可以是对象,例如 @article,把对象中的字段填入表单。如果传入一个和实例变量(@article)同名的 Symbol(:article),效果也是一样。上面的代码使用的就是 Symbol。详情参见 form_for 的文档

然后,要在 app/controllers/articles_controller.rb 中添加 update 动作:

def update
  @article = Article.find(params[:id])

  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

新定义的 update 方法用来处理对现有文章的更新操作,接收一个 Hash,包含想要修改的属性。和之前一样,如果更新文章出错了,要再次显示表单。

上面的代码再次使用了前面为 create 动作定义的 article_params 方法。

不用把所有的属性都提供给 update 动作。例如,如果使用 @article.update(title: 'A new title'),Rails 只会更新 title 属性,不修改其他属性。

最后,我们想在文章列表页面,在每篇文章后面都加上一个链接,指向 edit 动作。打开 app/views/articles/index.html.erb 文件,在“Show”链接后面添加“Edit”链接:

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
  </tr>
<% end %>
</table>

我们还要在 app/views/articles/show.html.erb 模板的底部加上“Edit”链接:

...

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

下图是文章列表页面现在的样子:

在文章列表页面显示了编辑链接

5.12 使用局部视图去掉视图中的重复代码

编辑文章页面和新建文章页面很相似,显示表单的代码是相同的。下面使用局部视图去掉两个视图中的重复代码。按照约定,局部视图的文件名以下划线开头。

关于局部视图的详细介绍参阅“Layouts and Rendering in Rails”一文。

新建 app/views/articles/_form.html.erb 文件,写入以下代码:

<%= form_for @article do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

除了第一行 form_for 的用法变了之外,其他代码都和之前一样。之所以能在两个动作中共用一个 form_for,是因为 @article 是一个资源,对应于符合 REST 架构的路由,Rails 能自动分辨使用哪个地址和请求方法。

关于这种 form_for 用法的详细说明,请查阅 API 文档

下面来修改 app/views/articles/new.html.erb 视图,使用新建的局部视图,把其中的代码全删掉,替换成:

<h1>New article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>

然后按照同样地方法修改 app/views/articles/edit.html.erb 视图:

<h1>Edit article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>

5.13 删除文章

现在介绍 CRUD 中的 D,从数据库中删除文章。按照 REST 架构的约定,删除文章的路由是:

DELETE /articles/:id(.:format)      articles#destroy

删除资源时使用 DELETE 请求。如果还使用 GET 请求,可以构建如下所示的恶意地址:

<a href='http://example.com/articles/1/destroy'>look at this cat!</a>

删除资源使用 DELETE 方法,路由会把请求发往 app/controllers/articles_controller.rb 中的 destroy 动作。destroy 动作现在还不存在,下面来添加:

def destroy
  @article = Article.find(params[:id])
  @article.destroy

  redirect_to articles_path
end

想把记录从数据库删除,可以在 Active Record 对象上调用 destroy 方法。注意,我们无需为这个动作编写视图,因为它会转向 index 动作。

最后,在 index 动作的模板(app/views/articles/index.html.erb)中加上“Destroy”链接:

<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
    <td><%= link_to 'Destroy', article_path(article),
                    method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

生成“Destroy”链接的 link_to 用法有点不一样,第二个参数是具名路由,随后还传入了几个参数。:method:'data-confirm' 选项设置链接的 HTML5 属性,点击链接后,首先会显示一个对话框,然后发起 DELETE 请求。这两个操作通过 jquery_ujs 这个 JavaScript 脚本实现。生成程序骨架时,会自动把 jquery_ujs 加入程序的布局中(app/views/layouts/application.html.erb)。没有这个脚本,就不会显示确认对话框。

确认对话框

恭喜,现在你可以新建、显示、列出、更新、删除文章了。

一般情况下,Rails 建议使用资源对象,而不手动设置路由。关于路由的详细介绍参阅“Rails 路由全解”一文。

6 添加第二个模型

接下来要在程序中添加第二个模型,用来处理文章的评论。

6.1 生成模型

下面要用到的生成器,和之前生成 Article 模型的一样。我们要创建一个 Comment 模型,表示文章的评论。在终端执行下面的命令:

$ rails generate model Comment commenter:string body:text article:references

这个命令生成四个文件:

文件 作用
db/migrate/20140120201010_create_comments.rb 生成 comments 表所用的迁移文件(你得到的文件名稍有不同)
app/models/comment.rb Comment 模型文件
test/models/comment_test.rb Comment 模型的测试文件
test/fixtures/comments.yml 测试时使用的固件

首先来看一下 app/models/comment.rb 文件:

class Comment < ActiveRecord::Base
  belongs_to :article
end

文件的内容和前面的 Article 模型差不多,不过多了一行代码:belongs_to :article。这行代码用来建立 Active Record 关联。下文会简单介绍关联。

除了模型文件,Rails 还生成了一个迁移文件,用来创建对应的数据表:

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body

      # this line adds an integer column called `article_id`.
      t.references :article, index: true

      t.timestamps
    end
  end
end

t.references 这行代码为两个模型的关联创建一个外键字段,同时还为这个字段创建了索引。下面运行这个迁移:

$ rake db:migrate

Rails 相当智能,只会执行还没有运行的迁移,在命令行中会看到以下输出:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

6.2 模型关联

使用 Active Record 关联可以轻易的建立两个模型之间的关系。评论和文章之间的关联是这样的:

  • 评论属于一篇文章
  • 一篇文章有多个评论

这种关系和 Rails 用来声明关联的句法具有相同的逻辑。我们已经看过 Comment 模型中那行代码,声明评论属于文章:

class Comment < ActiveRecord::Base
  belongs_to :article
end

我们要编辑 app/models/article.rb 文件,加入这层关系的另一端:

class Article < ActiveRecord::Base
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

这两行声明能自动完成很多操作。例如,如果实例变量 @article 是一个文章对象,可以使用 @article.comments 取回一个数组,其元素是这篇文章的评论。

关于 Active Record 关联的详细介绍,参阅“Active Record 关联”一文。

6.3 添加评论的路由

article 控制器一样,添加路由后 Rails 才知道在哪个地址上查看评论。打开 config/routes.rb 文件,按照下面的方式修改:

resources :articles do
  resources :comments
end

我们把 comments 放在 articles 中,这叫做嵌套资源,表明了文章和评论间的层级关系。

关于路由的详细介绍,参阅“Rails 路由全解”一文。

6.4 生成控制器

有了模型,下面要创建控制器了,还是使用前面用过的生成器:

$ rails generate controller Comments

这个命令生成六个文件和一个空文件夹:

文件/文件夹 作用
app/controllers/comments_controller.rb Comments 控制器文件
app/views/comments/ 控制器的视图存放在这个文件夹里
test/controllers/comments_controller_test.rb 控制器测试文件
app/helpers/comments_helper.rb 视图帮助方法文件
test/helpers/comments_helper_test.rb 帮助方法测试文件
app/assets/javascripts/comment.js.coffee 控制器的 CoffeeScript 文件
app/assets/stylesheets/comment.css.scss 控制器的样式表文件

在任何一个博客中,读者读完文章后就可以发布评论。评论发布后,会转向文章显示页面,查看自己的评论是否显示出来了。所以,CommentsController 中要定义新建评论的和删除垃圾评论的方法。

首先,修改显示文章的模板(app/views/articles/show.html.erb),允许读者发布评论:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

上面的代码在显示文章的页面添加了一个表单,调用 CommentsController 控制器的 create 动作发布评论。form_for 的参数是个数组,构建嵌套路由,例如 /articles/1/comments

下面在 app/controllers/comments_controller.rb 文件中定义 create 方法:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

这里使用的代码要比文章的控制器复杂得多,因为设置了嵌套关系,必须这么做评论功能才能使用。发布评论时要知道这个评论属于哪篇文章,所以要在 Article 模型上调用 find 方法查找文章对象。

而且,这段代码还充分利用了关联关系生成的方法。我们在 @article.comments 上调用 create 方法,创建并保存评论。这么做能自动把评论和文章联系起来,让这个评论属于这篇文章。

添加评论后,调用 article_path(@article) 帮助方法,转向原来的文章页面。前面说过,这个帮助函数调用 ArticlesControllershow 动作,渲染 show.html.erb 模板。我们要在这个模板中显示评论,所以要修改一下 app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

现在,可以为文章添加评论了,成功添加后,评论会在正确的位置显示。

文章的评论

7 重构

现在博客的文章和评论都能正常使用了。看一下 app/views/articles/show.html.erb 模板,内容太多。下面使用局部视图重构。

7.1 渲染局部视图中的集合

首先,把显示文章评论的代码抽出来,写入局部视图中。新建 app/views/comments/_comment.html.erb 文件,写入下面的代码:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

然后把 app/views/articles/show.html.erb 修改成:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

这个视图会使用局部视图 app/views/comments/_comment.html.erb 渲染 @article.comments 集合中的每个评论。render 方法会遍历 @article.comments 集合,把每个评论赋值给一个和局部视图同名的本地变量,在这个例子中本地变量是 comment,这个本地变量可以在局部视图中使用。

7.2 渲染局部视图中的表单

我们把添加评论的代码也移到局部视图中。新建 app/views/comments/_form.html.erb 文件,写入:

<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

然后把 app/views/articles/show.html.erb 改成:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render "comments/form" %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

第二个 render 方法的参数就是要渲染的局部视图,即 comments/form。Rails 很智能,能解析其中的斜线,知道要渲染 app/views/comments 文件夹中的 _form.html.erb 模板。

@article 变量在所有局部视图中都可使用,因为它是实例变量。

8 删除评论

博客还有一个重要的功能是删除垃圾评论。为了实现这个功能,要在视图中添加一个链接,并在 CommentsController 中定义 destroy 动作。

先在 app/views/comments/_comment.html.erb 局部视图中加入删除评论的链接:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

点击“Destroy Comment”链接后,会向 CommentsController 控制器发起 DELETE /articles/:article_id/comments/:id 请求。我们可以从这个请求中找到要删除的评论。下面在控制器中加入 destroy 动作(app/controllers/comments_controller.rb):

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

destroy 动作先查找当前文章,然后在 @article.comments 集合中找到对应的评论,将其从数据库中删掉,最后转向显示文章的页面。

8.1 删除关联对象

如果删除一篇文章,也要删除文章中的评论,不然这些评论会占用数据库空间。在 Rails 中可以在关联中指定 dependent 选项达到这一目的。把 Article 模型(app/models/article.rb)修改成:

class Article < ActiveRecord::Base
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

9 安全

9.1 基本认证

如果把这个博客程序放在网上,所有人都能添加、编辑、删除文章和评论。

Rails 提供了一种简单的 HTTP 身份认证机制可以避免出现这种情况。

ArticlesController 中,我们要用一种方法禁止未通过认证的用户访问其中几个动作。我们需要的是 http_basic_authenticate_with 方法,通过这个方法的认证后才能访问所请求的动作。

要使用这个身份认证机制,需要在 ArticlesController 控制器的顶部调用 http_basic_authenticate_with 方法。除了 indexshow 动作,访问其他动作都要通过认证,所以在 app/controllers/articles_controller.rb 中,要这么做:

class ArticlesController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # snipped for brevity

同时,我们还希望只有通过认证的用户才能删除评论。修改 CommentsController 控制器(app/controllers/comments_controller.rb):

class CommentsController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    ...
  end

  # snipped for brevity

现在,如果想新建文章,会看到一个 HTTP 基本认证对话框。

HTTP 基本认证对话框

其他的身份认证方法也可以在 Rails 程序中使用。其中两个比较流行的是 Devise 引擎和 Authlogic gem。

9.2 其他安全注意事项

安全,尤其是在网页程序中,是个很宽泛和值得深入研究的领域。Rails 程序的安全措施,在“Ruby on Rails 安全指南”中有更深入的说明。

10 接下来做什么

至此,我们开发了第一个 Rails 程序,请尽情地修改、试验。在开发过程中难免会需要帮助,如果使用 Rails 时需要协助,可以使用这些资源:

Rails 本身也提供了帮助文档,可以使用下面的 rake 任务生成:

  • 运行 rake doc:guides,会在程序的 doc/guides 文件夹中生成一份 Rails 指南。在浏览器中打开 doc/guides/index.html 可以查看这份指南。
  • 运行 rake doc:rails,会在程序的 doc/api 文件夹中生成一份完整的 API 文档。在浏览器中打开 doc/api/index.html 可以查看 API 文档。

使用 doc:guides 任务在本地生成 Rails 指南,要安装 RedCloth gem。在 Gemfile 中加入这个 gem,然后执行 bundle install 命令即可。

11 常见问题

使用 Rails 时,最好使用 UTF-8 编码存储所有外部数据。如果没使用 UTF-8 编码,Ruby 的代码库和 Rails 一般都能将其转换成 UTF-8,但不一定总能成功,所以最好还是确保所有的外部数据都使用 UTF-8 编码。

如果编码出错,常见的征兆是浏览器中显示很多黑色方块和问号。还有一种常见的符号是“ü”,包含在“ü”中。Rails 内部采用很多方法尽量避免出现这种问题。如果你使用的外部数据编码不是 UTF-8,有时会出现这些问题,Rails 无法自动纠正。

非 UTF-8 编码的数据经常来源于:

  • 你的文本编辑器:大多数文本编辑器(例如 TextMate)默认使用 UTF-8 编码保存文件。如果你的编辑器没使用 UTF-8 编码,有可能是你在模板中输入了特殊字符(例如 é),在浏览器中显示为方块和问号。这种问题也会出现在国际化文件中。默认不使用 UTF-8 保存文件的编辑器(例如 Dreamweaver 的某些版本)都会提供一种方法,把默认编码设为 UTF-8。记得要修改。
  • 你的数据库:默认情况下,Rails 会把从数据库中取出的数据转换成 UTF-8 格式。如果数据库内部不使用 UTF-8 编码,就无法保存用户输入的所有字符。例如,数据库内部使用 Latin-1 编码,用户输入俄语、希伯来语或日语字符时,存进数据库时就会永远丢失。如果可能,在数据库中尽量使用 UTF-8 编码。

反馈

欢迎帮忙改善指南质量。

如发现任何错误,欢迎修正。开始贡献前,可先行阅读贡献指南:文档

翻译如有错误,深感抱歉,欢迎 Fork 修正,或至此处回报

文章可能有未完成或过时的内容。请先检查 Edge Guides 来确定问题在 master 是否已经修掉了。再上 master 补上缺少的文件。内容参考 Ruby on Rails 指南准则来了解行文风格。

最后,任何关于 Ruby on Rails 文档的讨论,欢迎到 rubyonrails-docs 邮件群组