1 前提条件
本文针对想从零开始开发 Rails 程序的初学者,不需要预先具备任何的 Rails 使用经验。不过,为了能顺利阅读,还是需要事先安装好一些软件:
- Ruby 1.9.3 及以上版本</li>
- 包管理工具 RubyGems,随 Ruby 1.9+ 安装。想深入了解 RubyGems,请阅读 RubyGems 指南
- SQLite3 数据库</li>
</ul>
Rails 是使用 Ruby 语言开发的网页程序框架。如果之前没接触过 Ruby,学习 Rails 可要深下一番功夫。网上有很多资源可以学习 Ruby:
- Ruby 语言官方网站
- reSRC 列出的免费编程书籍</a></li>
</ul>
记住,某些资源虽然很好,但是针对 Ruby 1.8,甚至 1.6 编写的,所以没有介绍一些 Rails 日常开发会用到的句法。
2 Rails 是什么?
Rails 是使用 Ruby 语言编写的网页程序开发框架,目的是为开发者提供常用组件,简化网页程序的开发。只需编写较少的代码,就能实现其他编程语言或框架难以企及的功能。经验丰富的 Rails 程序员会发现,Rails 让程序开发变得更有乐趣。 Rails 有自己的一套规则,认为问题总有最好的解决方法,而且建议使用最好的方法,有些情况下甚至不推荐使用其他替代方案。学会如何按照 Rails 的思维开发,能极大提高开发效率。如果坚持在 Rails 开发中使用其他语言中的旧思想,尝试使用别处学来的编程模式,开发过程就不那么有趣了。 Rails 哲学包含两大指导思想:- 不要自我重复(DRY): DRY 是软件开发中的一个原则,“系统中的每个功能都要具有单一、准确、可信的实现。”。不重复表述同一件事,写出的代码才能更易维护,更具扩展性,也更不容易出问题。</li>
- 多约定,少配置: Rails 为网页程序的大多数需求都提供了最好的解决方法,而且默认使用这些约定,不用在长长的配置文件中设置每个细节。</li>
</ul>
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
$ sqlite3 --version
gem install
命令:$ gem install rails
$ rails --version
3.2 创建 Blog 程序
Rails 提供了多个被称为“生成器”的脚本,可以简化开发,生成某项操作需要的所有文件。其中一个是新程序生成器,生成一个 Rails 程序骨架,不用自己一个一个新建文件。 打开终端,进入有写权限的文件夹,执行以下命令生成一个新程序:$ rails new blog
blog
中新建一个 Rails 程序,然后执行bundle install
命令安装Gemfile
中列出的 gem。执行生成rails new -h
可以查看新程序生成器的所有命令行选项。blog
程序后,进入该文件夹:$ cd blog
blog
文件夹中有很多自动生成的文件和文件夹,组成一个 Rails 程序。本文大部分时间都花在app
文件夹上。下面简单介绍默认生成的文件和文件夹的作用:文件/文件夹</th> 作用 </tr> </thead>app/ 存放程序的控制器、模型、视图、帮助方法、邮件和静态资源文件。本文主要关注的是这个文件夹。</td> </tr> bin/ 存放运行程序的 rails
脚本,以及其他用来部署或运行程序的脚本。</td> </tr>config/ 设置程序的路由,数据库等。详情参阅“<a href="http://guides.ruby-china.org/configuring.html">设置 Rails 程序</a>”一文。</td> </tr> config.ru 基于 Rack 服务器的程序设置,用来启动程序。</td> </tr> db/ 存放当前数据库的模式,以及数据库迁移文件。</td> </tr> Gemfile, Gemfile.lock 这两个文件用来指定程序所需的 gem 依赖件,用于 Bundler gem。关于 Bundler 的详细介绍,请访问 Bundler 官网。</td> </tr> lib/ 程序的扩展模块。</td> </tr> log/ 程序的日志文件。</td> </tr> public/ 唯一对外开放的文件夹,存放静态文件和编译后的资源文件。</td> </tr> Rakefile 保存并加载可在命令行中执行的任务。任务在 Rails 的各组件中定义。如果想添加自己的任务,不要修改这个文件,把任务保存在 lib/tasks
文件夹中。</td> </tr>README.rdoc 程序的简单说明。你应该修改这个文件,告诉其他人这个程序的作用,如何安装等。</td> </tr> test/ 单元测试,固件等测试用文件。详情参阅“<a href="http://guides.ruby-china.org/testing.html">测试 Rails 程序</a>”一文。</td> </tr> tmp/ 临时文件,例如缓存,PID,会话文件。</td> </tr> vendor/ 存放第三方代码。经常用来放第三方 gem。</td> </tr> </tbody> </table> 4 Hello, Rails!
首先,我们来添加一些文字,在页面中显示。为了能访问网页,要启动程序服务器。 <h4 id="启动服务器">4.1 启动服务器</h4> 现在,新建的 Rails 程序已经可以正常运行。要访问网站,需要在开发电脑上启动服务器。请在blog
文件夹中执行下面的命令:$ rails server
把 CoffeeScript 编译成 JavaScript 需要 JavaScript 运行时,如果没有运行时,会报错,提示没有上述命令会启动 WEBrick,这是 Ruby 内置的服务器。要查看程序,请打开一个浏览器窗口,访问<a href="http://localhost:3000/">http://localhost:3000</a>。应该会看到默认的 Rails 信息页面:execjs
。Mac OS X 和 Windows 一般都提供了 JavaScript 运行时。Rails 生成的Gemfile
中,安装therubyracer
gem 的代码被注释掉了,如果需要使用这个 gem,请把前面的注释去掉。在 JRuby 中推荐使用therubyracer
。在 JRuby 中生成的Gemfile
已经包含了这个 gem。所有支持的运行时参见 ExecJS。要想停止服务器,请在命令行中按 Ctrl+C 键。服务器成功停止后回重新看到命令行提示符。在大多数类 Unix 系统中,包括 Mac OS X,命令行提示符是“欢迎使用”页面是新建 Rails 程序后的“冒烟测试”:确保程序设置正确,能顺利运行。你可以点击“About your application's environment”链接查看程序所处环境的信息。 <h4 id="显示“hello,-rails-bang”">4.2 显示“Hello, Rails!”</h4> 要在 Rails 中显示“Hello, Rails!”,需要新建一个控制器和视图。 控制器用来接受向程序发起的请求。路由决定哪个控制器会接受到这个请求。一般情况下,每个控制器都有多个路由,对应不同的动作。动作用来提供视图中需要的数据。 视图的作用是,以人类能看懂的格式显示数据。有一点要特别注意,数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来。默认情况下,视图使用 eRuby(嵌入式 Ruby)语言编写,经由 Rails 解析后,再发送给用户。 控制器可用控制器生成器创建,你要告诉生成器,我想要个名为“welcome”的控制器和一个名为“index”的动作,如下所示:$
符号。在开发模式中,一般情况下无需重启服务器,修改文件后,服务器会自动重新加载。$ rails generate controller welcome index
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
>
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'
#
# ...
root
开头的代码行,去掉注释,变成这样:root
'welcome#index'
root 'welcome#index'
告知 Rails,访问程序的根路径时,交给welcome
控制器中的index
动作处理。<code>get 'welcome/index'</code> 告知 Rails,访问 http://localhost:3000/welcome/index 时,交给welcome
控制器中的index
动作处理。<code>get 'welcome/index'</code> 是运行rails generate controller welcome index
时生成的。 如果生成控制器时停止了服务器,请再次启动(<code>rails server</code>),然后在浏览器中访问<a href="http://localhost:3000/">http://localhost:3000</a>。你会看到之前写入app/views/welcome/index.html.erb
文件的“Hello, Rails!”,说明新定义的路由把根目录交给WelcomeController
的index
动作处理了,而且也正确的渲染了视图。关于路由的详细介绍,请阅读“<a href="http://guides.ruby-china.org/routing.html">Rails 路由全解</a>”一文。<h3 id="开始使用">5 开始使用</h3> 前文已经介绍如何创建控制器、动作和视图,下面我们来创建一些更实质的功能。 在博客程序中,我们要创建一个新“资源”。资源是指一系列类似的对象,比如文章,人和动物。 资源可以被创建、读取、更新和删除,这些操作简称 CRUD。 Rails 提供了一个resources
方法,可以声明一个符合 REST 架构的资源。创建文章资源后,<code>config/routes.rb</code> 文件的内容如下: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
表单看起来很简陋,不过没关系,后文会加入更多的样式。 <h4 id="挖地基">5.1 挖地基</h4> 首先,程序中要有个页面用来新建文章。一个比较好的选择是
/articles/new
。这个路由前面已经定义了,可以访问。打开 http://localhost:3000/articles/new ,会看到如下的路由错误:产生这个错误的原因是,没有定义用来处理该请求的控制器。解决这个问题的方法很简单,执行下面的命令创建名为
ArticlesController
的控制器即可:$ bin/rails g controller articles
app/controllers/articles_controller.rb
文件,会看到一个几乎没什么内容的控制器:class
ArticlesController < ApplicationController
end
ApplicationController
。在这个类中定义的方法就是控制器的动作。动作的作用是处理文章的 CRUD 操作。在 Ruby 中,方法分为现在刷新 http://localhost:3000/articles/new,会看到一个新错误:public
、<code>private</code> 和protected
三种,只有public
方法才能作为控制器的动作。详情参阅 Programming Ruby 一书。这个错误的意思是,在刚生成的
ArticlesController
控制器中找不到new
动作。因为在生成控制器时,除非指定要哪些动作,否则不会生成,控制器是空的。 手动创建动作只需在控制器中定义一个新方法。打开app/controllers/articles_controller.rb
文件,在ArticlesController
类中,定义new
方法,如下所示:class
ArticlesController < ApplicationController
def
new
end
end
ArticlesController
中定义new
方法后,再刷新 http://localhost:3000/articles/new,看到的还是个错误: <img src="http://guides.ruby-china.org/images/getting_started/template_is_missing_articles_new.png" alt="找不到 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。<code>:locale</code> 表示要找哪国语言模板,默认是英语(<code>"en"</code>)。<code>:format</code> 表示响应使用的模板格式,默认为:html
,所以 Rails 要寻找一个 HTML 模板。<code>:handlers</code> 表示用来处理模板的程序,HTML 模板一般使用:erb
,XML 模板使用:builder
,<code>:coffee</code> 用来把 CoffeeScript 转换成 JavaScript。 最后一部分说明 Rails 在哪里寻找模板。在这个简单的程序里,模板都存放在一个地方,复杂的程序可能存放在多个位置。 让这个程序正常运行,最简单的一种模板是app/views/articles/new.html.erb
。模板文件的扩展名是关键所在:第一个扩展名是模板的类型,第二个扩展名是模板的处理程序。Rails 会尝试在app/views
文件夹中寻找名为articles/new
的模板。这个模板的类型只能是html
,处理程序可以是erb
、<code>builder</code> 或coffee
。因为我们要编写一个 HTML 表单,所以使用erb
。所以这个模板文件应该命名为articles/new.html.erb
,还要放在app/views
文件夹中。 新建文件app/views/articles/new.html.erb
,写入如下代码:<
h1
>New Article</
h1
>
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
%>
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
动作。5.3 创建文章
要解决前一节出现的错误,可以在ArticlesController
类中定义create
方法。在app/controllers/articles_controller.rb
文件中new
方法后面添加以下代码:class
ArticlesController < ApplicationController
def
new
end
def
create
end
end
create
动作改成:def
create
render plain: params[
:article
].inspect
end
render
方法接受一个简单的 Hash 为参数,这个 Hash 的键是plain
,对应的值为params[:article].inspect
。<code>params</code> 方法表示通过表单提交的参数,返回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
Article
模型,以及一个字符串属性title
和文本属性text
。这两个属性会自动添加到articles
数据表中,映射到Article
模型。 执行这个命令后,Rails 会生成一堆文件。现在我们只关注app/models/article.rb
和db/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
的方法,在运行迁移时执行。<code>change</code> 方法中定义的操作都是可逆的,Rails 知道如何撤销这次迁移操作。运行迁移后,会创建articles
表,以及一个字符串字段和文本字段。同时还会创建两个时间戳字段,用来跟踪记录的创建时间和更新时间。关于迁移的详细说明,请参阅“<a href="http://guides.ruby-china.org/migrations.html">Active Record 数据库迁移</a>”一文。然后,使用 rake 命令运行迁移:$ bin/rake db:migrate
articles
表。== CreateArticles: migrating ==================================================
-- create_table(:articles)
-> 0.0019s
== CreateArticles: migrated (0.0020s) =========================================
因为默认情况下,程序运行在开发环境中,所以相关的操作应用于<h4 id="在控制器中保存数据">5.6 在控制器中保存数据</h4> 再回到config/database.yml
文件中development
区域设置的数据库上。如果想在其他环境中运行迁移,必须在命令中指明:rake db:migrate RAILS_ENV=production
。ArticlesController
控制器,我们要修改create
动作,使用Article
模型把数据保存到数据库中。打开app/controllers/articles_controller.rb
文件,把create
动作修改成这样:def
create
@article
= Article.
new
(params[
:article
])
@article
.save
redirect_to
@article
end
show
动作。稍后再编写show
动作。后文会看到,再次访问 http://localhost:3000/articles/new,填写表单,还差一步就能创建文章了,会看到一个错误页面: <img src="http://guides.ruby-china.org/images/getting_started/forbidden_attributes_for_new_article.png" alt="新建文章时禁止使用属性" /> Rails 提供了很多安全防范措施保证程序的安全,你所看到的错误就是因为违反了其中一个措施。这个防范措施叫做“健壮参数”,我们要明确地告知 Rails 哪些参数可在控制器中使用。这里,我们想使用@article.save
返回一个布尔值,表示保存是否成功。title
和text
参数。请把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
方法了吗?这个方法允许在动作中使用title
和text
属性。注意,<code>article_params</code> 是私有方法。这种用法可以防止攻击者把修改后的属性传递给模型。关于健壮参数的更多介绍,请阅读这篇文章。5.7 显示文章
现在再次提交表单,Rails 会提示找不到show
动作。这个提示没多大用,我们还是先添加show
动作吧。 我们在rake routes
的输出中看到,<code>show</code> 动作的路由是: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
>
<h4 id="列出所有文章">5.8 列出所有文章</h4> 我们还要列出所有文章,对应的路由是:
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
<code