在线版的内容可能落后于电子书,如果想及时获得更新,请购买电子书

第 1 章 从零开始,完成一次部署

欢迎阅读《Ruby on Rails 教程:通过 Rails 学习 Web 开发》。本书的目的是教你开发 Web 应用,而我们选择的工具是流行的 Web 框架 Ruby on Rails。本书主要教你 Web 开发的一般性知识(不限定于 Rails),此外还会教你更多的技能,让你认清“技术是复杂的”(旁注 1.1)。这正是 Learn Enough to Be Dangerous 系列教程力图解决的问题。这一系列教程适合作为本书的预备知识阅读,最先发布的是《Learn Enough Command Line to Be Dangerous》,这个教程完全针对初学者(与市面上的书不同)。

本书会向你详细介绍 Web 应用开发的方方面面,包括 Ruby、Rails、HTML 和 CSS、数据库、版本控制、测试,以及部署的基本知识。学会这些知识足以为你赢得一份 Web 开发者的工作,或者还可以让你成为一名技术创业者。如果你已经了解 Web 开发,阅读本书能快速学会 Rails 框架的基础,包括 MVC 和 REST、生成器、迁移、路由,以及嵌入式 Ruby。不管怎样,读完本书之后,以你所掌握的知识,已经能够阅读讨论更高级话题的图书和博客,或者观看视频,这些都是繁荣的编程教学生态圈的一部分。

本书采用一种综合式方法讲授 Web 开发,在学习的过程中我们将开发三个演示应用:第一个最简单,叫 hello_app1.3 节);第二个功能多一些,叫 toy_app第 2 章);第三个是真正的演示应用,叫 sample_app第 3 章第 14 章)。从这三个应用的名字可以看出,书中开发的应用不限定于某种特定类型的网站。最后一个演示应用有点儿类似于 Twitter(很巧,这个网站起初也是使用 Rails 开发的)。不过,本书的重点是介绍通用原则,所以不管你想开发什么样的 Web 应用,读完本书后都能建立扎实的基础。

tech support cheat sheet
图 1.1:“技术支持速查表”(原图出自 xkcd[1]

在第 1 章,我们将安装 Ruby on Rails 以及所需的全部软件,而且还将搭建开发环境(1.2 节)。然后,我们将创建第一个 Rails 应用,名为 hello_app。本书旨在介绍优秀的软件开发实践,所以在创建第一个应用之后,我们会立即将它纳入版本控制系统 Git 中(1.4 节)。你可能不相信,在这一章,我们还将部署那个应用(1.5 节),把它放到网上。

第 2 章会创建第二个项目,演示 Rails 应用的一些基本操作。为了提升速度,我们将使用脚手架(旁注 1.2)创建那个应用(名为 toy_app)。因为脚手架生成的代码很丑也很复杂,所以第 2 章将集中精力在浏览器中,使用 URI(经常称为 URL)[2]与那个应用交互。

本书剩下的章节将集中精力开发一个真实的大型演示应用(名为 sample_app),所有代码都从零开始编写。在开发这个应用的过程中,我们会用到构思图、测试驱动开发(Test-driven Development,简称 TDD)理念和集成测试(integration test)。第 3 章创建静态页面,然后增加一些动态内容。第 4 章简要介绍 Rails 使用的 Ruby 程序语言。第 5 章第 12 章逐步完善这个应用的低层结构,包括网站的布局、User 数据模型,以及完整的注册和身份验证系统(含有账户激活和密码重设功能)。最后,第 13 章第 14 章添加微博和社交功能,最终开发出一个可以正常运行的演示网站。

1.1 简介

Ruby on Rails(或者简称 Rails)是一个 Web 开发框架,使用 Ruby 编程语言开发。自 2004 年出现之后,Rails 迅速成为动态 Web 应用开发领域功能最强大、最受欢迎的框架之一。使用 Rails 的公司有很多,例如 AirbnbBasecampDisneyGitHubHuluKickstarterShopifyTwitterYellow Pages。此外,还有很多 Web 开发工作室专门从事 Rails 应用开发,例如 ENTPthoughtbotPivotal LabsHashrocketHappyFunCorp。除此之外还有无数独立顾问、培训人员和项目承包商。

Rails 为何如此成功呢?首先,Rails 完全开源,基于宽松的 MIT 许可证发布,可以免费下载、使用。Rails 的成功很大程度上得益于它优雅而紧凑的设计。Rails 熟谙 Ruby 语言的可扩展性,开发了一套用于编写 Web 应用的领域特定语言(Domain-specific Language,简称 DSL)。所以 Web 编程中很多常见的任务,例如生成 HTML、创建数据模型和 URL 路由,在 Rails 中都很容易实现,最终得到的应用代码简洁而且可读性高。

Rails 还会快速跟进 Web 开发领域最新的技术和框架设计方式。例如,Rails 是最早使用 REST 架构风格组织 Web 应用的框架之一(这个架构贯穿本书)。当其他框架开发出成功的新技术后,Rails 的创建者 David Heinemeier Hansson 及其核心开发团队会毫不犹豫地将其吸纳进来。或许最典型的例子是 Rails 和 Merb 两个项目的合并,自此 Rails 继承了 Merb 的模块化设计、稳定的 API,性能也得到了提升。

最后一点,Rails 社区特别热情,乐于助人。社区中有数以千计的开源贡献者,以及与会者众多的开发者大会,而且还开发了大量的 gem(代码库,一个 gem 解决一个特定的问题,例如分页和图像上传),有很多内容丰富的博客,以及一些讨论组和 IRC 频道。有如此多的 Rails 程序员也使得处理应用错误变得简单了:在 Google 中搜索错误消息,几乎总能找到一篇相关的博客文章或讨论组中的话题。

1.1.1 预备知识

阅读本书不需要具备特定的预备知识。本书不仅介绍 Rails,还涉及底层的 Ruby 语言、Rails 默认使用的测试框架(MiniTest)、Unix 命令行、HTMLCSS、少量的 JavaScript,以及一点 SQL。我们要掌握的知识很多,所以我一般建议阅读本书之前先具备一些 HTML 和编程知识。如果你刚接触软件开发,建议你先阅读 Learn Enough to Be Dangerous 系列教程。

  1. 开发者基础知识

  2. Web 基础知识

  3. 开发入门

  4. 应用开发

学习 Rails 时经常有人问,要不要先学 Ruby?这个问题的答案取决于你个人的学习方式以及编程经验。如果你希望较为系统地彻底学习,或者以前从未编程过,那么先学 Ruby 或许更合适。学习 Ruby,推荐你按照上述列表中的顺序阅读整个 Learn Enough 系列教程。很多 Rails 初学者很想立即着手开发 Web 应用,而不是在此之前先读完一本介绍 Ruby 的书。如果你是这类人,建议你先把本书过一遍,如果觉得太难,再回过头去阅读 Learn Enough 系列教程。

不管你从哪里开始,读完本书后都应该可以接着学习 Rails 中高级知识了。以下是我特别推荐的学习资源:

  • Learn Enough Society:这是一项收费订阅服务,包含本书的特别增强版和 15 个多小时的流媒体视频课程。视频中介绍了众多技巧,还有真人演示,这是阅读纸质书不可获得的。这项服务还包括 Learn Enough 系列教程的文字版和视频。提供教育优惠。

  • Code School:很好的交互式编程课程;

  • Bloc:一个在线训练营,有结构化的课程、个人导师,通过具体的项目学习知识。使用 BLOCLOVESHARTL 优惠码可以节省 $500 报名费。

  • Launch School:不错的在线 Rails 开发训练营(包括高级课程)

  • Firehose Project:导师制在线编程训练营,专注于具体的编程技能,如测试驱动开发、算法,以及敏捷 Web 应用开发。有两周免费的入门课程。

  • Thinkful:在线课程,由专业的工程师辅导开发项目;

  • Pragmatic Studio:Mike 和 Nicole Clark 主讲的在线 Ruby 和 Rails 课程。Mike 与《Programming Ruby》的作者 Dave Thomas 主讲的 Rails 教程是我学习的第一个 Rails 课程,历史可追溯到 2006 年。

  • RailsApps:很多针对特定话题的 Rails 项目和教程,说明详细;

  • Rails 指南:按话题编写的 Rails 参考,经常更新

练习

书中有大量练习,强烈建议你在阅读的过程中做这些练习。

为了避免练习妨碍主线,解答通常不会影响后续的代码清单。不过,有极少数的练习解答在后面要用到,此时正文中会给出解答。

如果你想记录自己的答案,想查看解答,可以加入 Learn Enough Society。这是 Learn Enough to Be Dangerous 推出的一项订阅服务,包含本书的特别增强版。

很多练习具有一定难度,我们先来做几个简单的,热热身:

  1. Ruby on Rails 的 Ruby gem 托管在哪个网站中?提示:如果不知道,使用 Google 搜索

  2. Rails 目前的版本号是多少?

  3. 截至目前,Ruby on Rails 总计被下载了多少次?

1.1.2 排版约定

本书使用的排版方式,很多都不用再做解释。本节说一下那些意义不是很清晰的排版。

书中很多代码清单用到了命令行命令。为了行文简便,所有命令都使用 Unix 风格命令行提示符(一个美元符号),如下所示:

$ echo "hello, world!"
hello, world!

Rails 提供了很多可以在命令行中运行的命令。例如,在 1.3.2 节,我们会使用 rails server 命令启动本地 Web 开发服务器:

$ rails server

与命令行提示符一样,本书也使用 Unix 惯用的目录分隔符(即斜线 /)。例如,演示应用的配置文件 production.rb,它的路径是:

config/environments/production.rb

上述文件路径相对于应用的根目录而言。在不同的系统中,根目录会有差别。在云端 IDE 中(1.2.1 节),根目录像下面这样:

/home/ec2-user/environment/sample_app/

所以,production.rb 文件的完整路径是:

/home/ec2-user/environment/sample_app/config/environments/production.rb

通常,我会省略应用的路径,简写成 config/environments/production.rb

本书经常需要显示一些来自其他程序的输出。因为系统之间存在细微的差异,你看到的输出结果可能和书中显示的不完全一致,但是无需担心。而且,有些命令在某些操作系统中可能会导致错误,本书不会一一说明这些错误的解决方法,你可以在 Google 中搜索错误消息,自己尝试解决——这也是为现实中的软件开发做准备。如果你在阅读本书的过程中遇到了问题,我建议你看一下本书网站帮助区中列出的资源。

本书涵盖 Rails 应用测试,所以最好知道某段代码能让测试组件失败(使用红色表示)还是通过(使用绿色表示)。为了方便,导致测试失败的代码使用“RED”标记,能让测试通过的代码使用“GREEN”标记。

最后,为了方便,本书使用两种排版方式,以便让代码清单更易于理解。第一种,有些代码清单中包含一到多个突出的代码行,如下所示:

class User < ApplicationRecord
  validates :name,  presence: true
  validates :email, presence: true
end

突出的代码行一般用于标出这段代码清单中最重要的新代码,偶尔也用来表示当前代码清单和之前的代码清单的差异。第二种,为了行文简洁,书中很多代码清单中都有竖排的点号,如下所示:

class User < ApplicationRecord
  .
  .
  .
  has_secure_password
end

这些点号表示省略的代码,不要直接复制。

1.2 搭建环境

就算对经验丰富的 Rails 开发者来说,安装 Ruby、Rails,以及相关的所有软件,也要几经波折。这些问题是由环境的多样性导致的。不同的操作系统、版本号、文本编辑器的偏好设置和集成开发环境(Integrated Development Environment,简称 IDE)等,都会导致环境有所不同。为此,本书提供了两种推荐的解决方案。第一种方案是,按照 1.1.1 节提到的 Learn Enough 系列教程做,最终你能搭建出本书所需的环境。

第二种方案针对初学者,建议这些人使用云端集成开发环境,从而避免安装和配置问题。本书使用的云端 IDE 运行在普通的 Web 浏览器中,因此在不同的平台中表现一致,这对 Rails 开发一直很困难的操作系统(例如 Windows)来说尤其有用。此外,这个云端 IDE 还能保存当前的工作状态,因此我们可以休息一会,然后再从离开的地方继续学习。

1.2.1 开发环境

不同的人有不同的喜好,每个 Rails 程序员都有自己的一套开发环境。为了避免问题复杂化,本书使用一个标准的云端开发环境——Cloud9(隶属 Amazon Web Services)。[3]这个开发环境预装了 Rails 开发所需的大多数软件,包括 Ruby、RubyGems 和 Git(其实,唯有 Rails 要单独安装,而且这么做是有目的的,详情参见 1.2.2 节)。

虽然可以在本地开发应用,但是搭建 Rails 开发环境有一定的挑战,所以我建议多数读者使用这个云端 IDE。如果你确实想在本地开发,可以跟着 Learn Enough Dev Environment to Be Dangerous 中的步骤搭建试试——这个过程能让你体会到技术是复杂的(旁注 1.1)。

这个云端 IDE 包含 Web 应用开发所需的三个基本组件:文本编辑器、文件系统浏览器和命令行终端(图 1.2)。云端 IDE 中的文本编辑器功能很多,其中一项是“在文件中查找”的全局搜索功能[4],我觉得这个功能对大型 Rails 项目来说是必备的。即便最终在现实中你不使用云端 IDE(我始终建议不断学习其他工具),通过它也能了解文本编辑器和其他开发工具的基本功能。

ide anatomy aws
图 1.2:云端 IDE 的界面布局

这个云端开发环境的使用步骤如下:[5]

  1. Cloud9 隶属 Amazon Web Services(AWS),如果已有 AWS 账户,可以直接登录。为了新建 Cloud9 工作环境,访问 AWS 控制台,在搜索框中输入“Cloud9”。

  2. 如果没有 AWS 账户,可以到 AWS Cloud9 网站注册一个免费账户。未免被滥用,AWS 要求注册时提供有效的信用卡,不过工作空间是 100% 免费的(写作本书时是免费一年),不会从信用卡上扣钱。可能要等 24 小时待账户激活,不过我就等了十分钟。

  3. 进入 Cloud9 管理页面后(图 1.3)后,点击“Create environment”(创建环境),直到看到类似 图 1.4 的界面。输入图中所示的信息,然后点击确认按钮,直到 Cloud9 开始配置 IDE(图 1.5)。你可能会遇到一个提醒消息,说要“root”用户,目前可以放心忽略。[我们将在 13.4.4 节讨论推荐但较为复杂的做法,叫做 Identity and Access Management(IAM)用户。]

cloud9 page aws
图 1.3:Cloud9 的管理页面
cloud9 name environment
图 1.4:在 AWS Cloud9 中新建一个工作环境
cloud9 ide aws
图 1.5:默认的云端 IDE
cloud9 two spaces aws
图 1.6:让 Cloud9 使用两个空格缩进

因为使用两个空格缩进几乎是 Ruby 圈通用的约定,所以我建议你修改编辑器的配置,把默认的四个空格改为两个。配置方法是,点击右上角的齿轮图标,然后选择“Code Editor (Ace)”(Ace 代码编辑器),编辑“Soft Tabs”(软制表符)设置,如图 1.6 所示。(注意,设置修改后立即生效,无需点击“Save”按钮。)

使用云端 IDE 的用户还要仔细阅读针对 Bitbucket(1.4.3 节)和 Heroku(1.5.1 节)的安装说明,这部分内容自 Cloud9 服务独立之后有一些变化。

最后,想像 3.6 节那样设置测试的高级用户可能还要在云端 IDE 中做些额外配置,详情参见 3.6.2 节

1.2.2 安装 Rails

前一节创建的开发环境包含所有软件,但没有 Rails。首先要做些准备工作,以免浪费时间在本地安装 Ruby 文档。这项配置每个系统只需做一次(因此也就无需费心去理解下述命令的作用),如下所示:[6]

$ printf "install: --no-rdoc --no-ri\nupdate:  --no-rdoc --no-ri\n" >> ~/.gemrc

为了安装 Rails,我们要使用包管理器 RubyGems 提供的 gem 命令,在命令行终端里输入代码清单 1.1 所示的命令。(如果在本地系统中开发,在终端窗口中输入这个命令;如果使用云端 IDE,在图 1.2 中的“命令行终端”区域输入这个命令。)

代码清单 1.1:安装指定版本的 Rails
$ gem install rails -v 5.1.6

-v 旗标的作用是指定安装哪个 Rails 版本。你使用的版本必须和我一样,这样学习的过程中,你我得到的结果才相同。

1.3 第一个应用

按照计算机编程领域长期沿用的传统,第一个应用的目的是编写一个“hello, world”程序。具体来讲,我们将创建一个简单的应用,在网页中显示字符串“hello, world!”,在开发环境(1.3.4 节)和线上网站(1.5 节)中都是如此。

Rails 应用一般都从 rails new 命令开始,这个命令会在你指定的目录中创建 Rails 应用的骨架。如果你没使用 1.2.1 节推荐的 Cloud9 IDE,首先要新建一个目录,命名为 environment,然后进入目录,如代码清单 1.2 所示。(代码清单 1.2 中使用了 Unix 命令 cdmkdir,如果你不熟悉这些命令,请阅读旁注 1.3。)

代码清单 1.2:为 Rails 项目新建一个目录,命名为 environment(在云端环境中不用做这一步)
$ cd                  # 进入家目录
$ mkdir environment   # 新建 environment 目录
$ cd environment/     # 进入 environment 目录
表 1.1:一些常用的 Unix 命令
作用 命令 示例

列出内容

ls

$ ls -l

新建目录

mkdir <dirname>

$ mkdir environment

变换目录

cd <dirname>

$ cd environment/

进入上层目录

$ cd ..

进入家目录

$ cd ~$ cd

进入家目录中的文件夹

$ cd ~/environment/

移动文件(重命名)

mv <source> <target>

$ mv foo bar

复制文件

cp <source> <target>

$ cp foo bar

删除文件

rm <file>

$ rm foo

删除空目录

rmdir <directory>

$ rmdir environment/

删除非空目录

rm -rf <directory>

$ rm -rf tmp/

拼接并显示文件的内容

cat <file>

$ cat ~/.ssh/id_rsa.pub

不管在本地还是在云端 IDE 中,下一步都是使用代码清单 1.3中的命令创建第一个应用。注意,在这个代码清单中,我们明确指定了 Rails 版本。这么做的目的是,确保使用代码清单 1.1 中安装的 Rails 版本来创建应用的文件结构。(执行代码清单 1.3 中的命令时,如果返回“Could not find 'railties'”这样的错误,说明你没安装正确的 Rails 版本,请再次确认你安装 Rails 时执行的命令与代码清单 1.1 完全一样。)

代码清单 1.3:执行 rails new 命令(明确指定版本号)
$ cd ~/environment
$ rails _5.1.6_ new hello_app
      create
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      .
      .
      .
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
      remove  config/initializers/cors.rb
         run  bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
Installing rake 11.1.2
Using concurrent-ruby 1.0.2
.
.
.
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

代码清单 1.3 所示,执行 rails new 命令生成所有文件之后,会自动执行 bundle install 命令。我们将在 1.3.1 节说明这个命令的作用。

留意一下 rails new 命令创建的文件和目录。这个标准的文件结构(图 1.7)是 Rails 的众多优势之一:让你从零开始快速创建一个可运行的功能最简的应用。而且,所有 Rails 应用都使用这种文件结构,阅读他人的代码时很快就能理清头绪。

directory structure rails 4th edition
图 1.7:新建 Rails 应用时生成的目录结构

这些文件的作用如表 1.2 所示,本书后面的内容将介绍其中大多数文件和目录。从 5.2.1 节开始,我们将介绍 app/assets 目录,这是 Asset Pipeline 的一部分。Asset Pipeline 简化了层叠样式表和 JavaScript 等静态资源文件的组织和部署方式。

表 1.2:Rails 目录结构简介
文件/文件夹 作用

app/

应用的核心文件,包含模型、视图、控制器和辅助方法

app/assets

应用的静态资源文件,例如层叠样式表(CSS)、JavaScript 文件和图像

bin/

可执行的二进制文件

config/

应用的配置

db/

数据库文件

doc/

应用的文档

lib/

代码库模块文件

lib/assets

代码库的静态资源文件,例如层叠样式表(CSS)、JavaScript 文件和图像

log/

应用的日志文件

public/

公共(如浏览器)可访问的文件,例如错误页面

bin/rails

生成代码、打开终端会话或启动本地服务器的程序

test/

应用的测试

tmp/

临时文件

vendor/

第三方代码,例如插件和 gem

vendor/assets

第三方静态资源文件,例如层叠样式表(CSS)、JavaScript 文件和图像

README.md

应用简介

Rakefile

使用 rake 命令执行的实用任务

Gemfile

应用所需的 gem

Gemfile.lock

gem 列表,确保这个应用的副本使用相同版本的 gem

config.ru

Rack 中间件的配置文件

.gitignore

Git 忽略的文件模式

1.3.1 Bundler

创建完一个新的 Rails 应用后,下一步是使用 Bundler 安装和引入该应用所需的 gem。1.3 节简单提到过,执行 rails new 命令时会自动运行 Bundler(bundle install 命令)。不过这一节,我们要修改应用默认使用的 gem,然后再次运行 Bundler。首先,在文本编辑器中打开 Gemfile 文件。(在云端 IDE 中要点击文件系统浏览器中的应用目录,然后双击 Gemfile 文件。)虽然具体的版本号和内容或许有所不同,但大概与代码清单 1.4图 1.8 差不多。(这个文件中的内容是 Ruby 代码,现在先不关心句法,第 4 章会详细介绍 Ruby。)如果你没看到如图 1.8 所示的文件和目录,点击文件浏览器中的齿轮图标,然后选择“Refresh File Tree”(刷新文件树)。(通常,如果某个文件或目录没出现,就可以刷新文件树。)[7]

代码清单 1.4hello_app 目录中默认生成的 Gemfile 文件
source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.6'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster.
# Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution
  # and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13'
  gem 'selenium-webdriver'
end

group :development do
  # Access an IRB console on exception pages or by using
  # <%= console %> anywhere in the code.
  gem 'web-console'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running
  # in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

其中很多行代码都用 # 符号注释掉了(4.2.1 节),这些代码放在这是为了告诉你一些常用的 gem,也是为了展示 Bundler 的句法。现在,除了这些默认的 gem 之外,我们还不需要其他的 gem。

如果没在 gem 指令中指定版本号,Bundler 会自动安装最新版。下面就是一例:

gem 'sqlite3'
cloud9 gemfile aws
图 1.8:在文本编辑器中打开默认生成的 Gemfile 文件

还有两种常用的方法,用于指定 gem 版本的范围,在一定程度上控制 Rails 使用的版本。首先看下面这行代码:

gem 'uglifier', '>= 1.3.0'

这行代码的意思是,安装版本号大于或等于 1.3.0uglifier(作用是压缩 Asset Pipeline 中的文件),就算是 7.2 版也会安装。第二种方法如下所示:

gem 'coffee-rails', '~> 4.0.0'

这行代码的意思是,安装版本号大于 4.0.0,但小于 4.1coffee-rails。也就是说,>= 表示法的意思是始终安装最新版;~> 4.0.0 表示法的意思是只安装最后一个数字变化的版本(例如从 4.0.04.0.1),而不安装前面的数字有变的更新(例如从 4.04.1)。不过,经验告诉我们,即使是最小版本的升级也可能导致错误,所以在本教程中我们基本上会为所有的 gem 都指定精确的版本号。你可以使用任何 gem 的最新版本,还可以在 Gemfile 文件中使用 ~>(一般推荐有经验的用户使用),但事先提醒你,这可能会导致本教程开发的应用表现异常。

修改代码清单 1.4 中的 Gemfile 文件,换用精确的版本号,得到的结果如代码清单 1.5 所示。注意,借此机会我们还变动了 sqlite3 gem 的位置,只在开发环境和测试环境(7.1.1 节)中安装,从而避免跟 Heroku 所用的数据库冲突(1.5 节)。

代码清单 1.5:在 Gemfile 文件中为所有 gem 指定精确的版本号
source 'https://rubygems.org'

gem 'rails',        '5.1.6'
gem 'puma',         '3.9.1'
gem 'sass-rails',   '5.0.6'
gem 'uglifier',     '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks',   '5.0.1'
gem 'jbuilder',     '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

代码清单 1.5 中的内容写入应用的 Gemfile 文件之后,执行 bundle install 命令[8]安装这些 gem:

$ cd hello_app/
$ bundle install
Fetching source index for https://rubygems.org/
.
.
.

bundle install 命令可能要执行一会儿,不过结束后我们的应用就能运行了。

顺便说一下,执行 bundle install 命令时可能会提醒你先执行 bundle update 命令。此时,应该按照提醒,先执行 bundle update。(在事情不能按计划进行时要学着不要惊慌,这是应对“技术是复杂的”这一问题的关键。冷静下来你便会惊奇地发现,“错误”消息中往往包含修正问题的具体说明。)

1.3.2 rails server

运行完 1.3 节中的 rails new 命令和 1.3.1 节中的 bundle install 命令之后,我们的应用就可以运行了。但是怎么运行呢?Rails 自带了一个命令行程序(或叫脚本),可以运行一个本地服务器,协助我们的开发工作。这个命令是 rails server代码清单 1.6)。

代码清单 1.6:运行 Rails 服务器
$ cd ~/environment/hello_app/
$ rails server
=> Booting Puma
=> Rails application starting on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server

如果系统提示缺少 JavaScript 运行时,访问 execjs 在 GitHub 中的项目主页,查看解决方法。我非常推荐安装 Node.js

我建议你在另一个终端标签页中执行 rails server 命令,这样你就可以继续在第一个标签页中执行其他命令了,如图 1.9图 1.10 所示。(如果你已经在第一个标签页中启动了服务器,可以按 Ctrl-C 键关闭服务器。[9])在本地环境中,把 http://0.0.0.0:3000 粘贴到浏览器的地址栏中;在云端 IDE 中,打开“Preview”(预览)菜单,点击“Preview Running Application”(预览运行中的应用)(图 1.11),在新窗口或标签页中打开(图 1.12)。在这两种环境中,显示的页面应该都与图 1.13 类似。

练习
  1. 根据 Rails 默认页面中的信息,你的系统使用的 Ruby 是哪个版本?在命令行中执行 ruby -v 命令确认一下。

  2. Rails 是哪个版本?确认是否与代码清单 1.1 中指定的版本一样。

new terminal tab aws
图 1.9:再打开一个终端标签页
rails server new tab aws
图 1.10:在另一个标签页中运行 Rails 服务器
share workspace aws
图 1.11:分享运行在云端工作空间中的本地服务器
full browser window aws
图 1.12:在新窗口或标签页中打开运行中的应用
riding rails 4th edition
图 1.13:执行 rails server 命令后看到的 Rails 默认页面

1.3.3 模型-视图-控制器

在初期阶段,概览一下 Rails 应用的工作方式(图 1.14)多少会有些帮助。你可能已经注意到了,在 Rails 应用的标准文件结构中有一个名为 app/ 的目录(图 1.7),其中有三个子目录:modelsviewscontrollers。这表明 Rails 采用了“模型-视图-控制器”(简称 MVC)架构模式。这种模式把应用中的数据(例如用户信息)与显示数据的代码分开,这是图形用户界面(Graphical User Interface,简称 GUI)常用的架构方式。

mvc schematic
图 1.14:MVC 架构图解

与 Rails 应用交互时,浏览器发出一个请求(request),Web 服务器收到请求之后将其传给 Rails 应用的控制器,决定下一步做什么。某些情况下,控制器会立即渲染视图(view),生成 HTML,然后发送给浏览器。在动态网站中,更常见的是控制器与模型(model)交互。模型是一个 Ruby 对象,表示网站中的一个元素(例如一个用户),并且负责与数据库通信。与模型交互后,控制器再渲染视图,把生成的 HTML 返回给浏览器。

如果你觉得这些内容有点抽象,不用担心,后面会进一步讨论这些概念。在 1.3.4 节,我们将首次使用 MVC 架构编写应用;在 2.2.2 节中,会以一个应用为例较为深入地讨论 MVC;在最后那个演示应用中会使用完整的 MVC 架构。从 3.2 节开始,介绍控制器和视图;从 6.1 节开始,介绍模型;7.1.2 节则把这三部分结合在一起使用。

1.3.4 Hello, world!

接下来我们要对这个使用 MVC 框架开发的第一个应用做些小改动:添加一个控制器动作(controller action),渲染字符串“hello, world!”,以此替代 Rails 的默认页面(图 1.13)。(从 2.2.2 节开始,我们将深入学习控制器动作。)

从“控制器动作”这个名字可以看出,动作在控制器中定义。我们要在 Application 控制器中定义这个动作,将其命名为 hello。其实,现在我们的应用只有这一个控制器。执行下述命令可以验证这一点:

$ ls app/controllers/*_controller.rb

hello 动作的定义如代码清单 1.7 所示,它调用 render 方法返回 HTML 文本“hello, world!”。(现在先不管 Ruby 的句法,第 4 章会详细介绍。)

代码清单 1.7:在 ApplicationController 中添加 hello 动作
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def hello
    render html: "hello, world!"
  end
end

定义好返回所需字符串的动作之后,我们要告诉 Rails 使用这个动作,而不再显示默认的首页(图 1.13)。为此,我们要修改 Rails 路由器(router)。路由器在控制器之前(图 1.14),决定浏览器发给应用的请求由哪个动作处理。(简单起见,图 1.14 中省略了路由器,从 2.2.2 节开始会详细介绍路由器。)具体而言,我们要修改默认的首页,也就是根路由(root route)。这个路由决定根 URL 显示哪个页面。根 URL 是 http://www.example.com/ 这种形式(最后一个斜线后面没有任何内容),所以经常简化使用 /(斜线)表示。

代码清单 1.8 所示,Rails 路由文件(config/routes.rb)中有一行注释,让我们阅读 Rails 指南中讲解路由的文章。那篇文章说明了如何定义根路由,句法如下:

root 'controller_name#action_name'

这里,控制器的名称是 application,动作的名称是 hello,因此根路由要像代码清单 1.9 那样定义。

代码清单 1.8:默认的路由文件(我重新编排了)
config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file,
  # see http://guides.rubyonrails.org/routing.html
end
代码清单 1.9:设置根路由
config/routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end
hello world hello app
图 1.15:在浏览器中查看显示“hello, world!”的页面

有了代码清单 1.7代码清单 1.9 中的代码,根路由就会按照我们的要求显示“hello, world!”了,如图 1.15 所示。[10]

练习
  1. hello 动作(代码清单 1.7)中的“hello, world!”改成“hola, mundo!”。

  2. 使用倒置的感叹号(如“¡Hola, mundo!”中的第一个字符),证明 Rails 支持非 ASCII 字符。[11]结果如图 1.16 所示。在 Mac 中输入 ¡ 字符的方法是按 Option-1 键;此外,也可以直接把这个字符复制粘贴到编辑器中。

  3. 按照编写 hello 动作的方式(代码清单 1.7),再添加一个动作,命名为 goodbye,渲染文本“goodbye, world!”。然后修改路由文件(代码清单 1.9),把根路由改成 goodbye。结果如图 1.17 所示。

hola mundo
图 1.16:修改根路由,返回“¡Hola, mundo!”
goodbye world
图 1.17:修改根路由,返回“goodbye, world!”

1.4 使用 Git 做版本控制

我们创建了一个“hello, world”应用,接下来要花点时间做一件事。虽然这件事不是必须的,但是经验丰富的软件开发者都认为这是最基本的事情,即把应用的源代码纳入版本控制系统。版本控制系统可以跟踪项目中代码的变化,便于和他人协作;如果出现问题(例如不小心删除了文件),还可以回滚到以前的版本。每个专业的软件开发者都应该学习使用版本控制系统。

版本控制系统种类很多,Rails 社区基本都使用 Git。Git 是一个分布式版本控制系统,由 Linus Torvalds 开发,最初的目的是存储 Linux 内核代码。Git 相关的知识很多,本书只会介绍一些皮毛。如果想深入了解,请阅读《Learn Enough Git to Be Dangerous》。

之所以强烈推荐使用 Git 做版本控制,不仅因为 Rails 社区都在用,还因为使用 Git 更易于分享代码(1.4.3 节),而且也便于部署应用(1.5 节)。

1.4.1 安装和设置

1.2.1 节推荐使用的云端 IDE 默认自带 Git,不用再安装。如果你没使用云端 IDE,可以参照《Learn Enough Git to Be Dangerous》中的说明,在自己的系统中安装 Git。

第一次运行前要做的系统设置

使用 Git 之前,要做些一次性设置。这些设置对整个系统都有效,因此一台电脑只需设置一次:

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com

注意,在 Git 配置中设定的名字和电子邮件地址会在所有公开的仓库中显示。

第一次使用仓库前要做的设置

下面的步骤每次新建仓库(repository,有时简称 repo)时都要执行。首先进入第一个应用的根目录,初始化一个新仓库:

$ git init
Initialized empty Git repository in /home/ec2-user/environment/environment/hello_app/.git/

然后执行 git add -A 命令,把项目中的所有文件都放到仓库中:

$ git add -A

这个命令会把当前目录中的所有文件都放到仓库中,但是匹配特殊文件 .gitignore 中模式的文件除外。rails new 命令会自动生成一个适用于 Rails 项目的 .gitignore 文件,此外你还可以添加其他模式。[12]

加入仓库的文件一开始位于暂存区(staging area),这一区用于存放待提交的内容。执行 status 命令可以查看暂存区中有哪些文件:

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

  new file:   .gitignore
  new file:   Gemfile
  new file:   Gemfile.lock
  new file:   README.md
  new file:   Rakefile
  .
  .
  .

如果想让 Git 保存这些改动,使用 commit 命令:

$ git commit -m "Initialize repository"
[master (root-commit) df0a62f] Initialize repository
.
.
.

-m 旗标的意思是为这次提交添加一个说明。如果没指定 -m 旗标,Git 会打开系统默认使用的编辑器,让你在其中输入说明。(本书所有的示例都会使用 -m 旗标。)

有一点要特别注意:Git 提交只发生在本地,也就是说只在执行提交操作的设备中存储内容。1.4.4 节会介绍如何把改动推送到远程仓库(使用 git push 命令)。

顺便说一下,可以使用 log 命令查看提交历史:

$ git log
commit af72946fbebc15903b2770f92fae9081243dd1a1
Author: Michael Hartl <michael@michaelhartl.com>
Date:   Thu May 12 19:25:07 2016 +0000

    Initialize repository

如果仓库的提交历史很多,可能需要输入 q 退出。(《Learn Enough Git to Be Dangerous》说道,git log 用到了《Learn Enough Command Line to Be Dangerous》中介绍的 less 接口。)

1.4.2 使用 Git 有什么好处

如果以前从未用过版本控制系统,现在可能不完全明白版本控制的好处。那我举个例子说明一下吧。假如你不小心做了某个操作,例如把重要的 app/controllers/ 目录删除了:

$ ls app/controllers/
application_controller.rb  concerns/
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory

这里,我们用 Unix 中的 ls 命令列出 app/controllers/ 目录里的内容,然后用 rm 命令删除这个目录(表 1.1)。如《Learn Enough Command Line to Be Dangerous》所述,-rf 旗标的意思是“强制递归”,无需明确征求同意就递归删除所有文件、目录和子目录等。

检查状态,看看发生了什么:

$ git status
On branch master
Changed but not updated:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

      deleted:    app/controllers/application_controller.rb

no changes added to commit (use "git add" and/or "git commit -a")

可以看出,我们删除了一个文件。但是这个改动只发生在工作区(working tree)中,还未提交到仓库。这意味着,我们可以使用 checkout 命令,并指定 -f 旗标,强制撤销这次改动:

$ git checkout -f
$ git status
# On branch master
nothing to commit (working directory clean)
$ ls app/controllers/
application_controller.rb  concerns/

删除的目录和文件又回来了,这下放心了!

1.4.3 Bitbucket

我们已经把项目纳入 Git 版本控制系统了,接下来可以把代码推送到 Bitbucket。这是一个专门用来托管和分享 Git 仓库的网站。(《Learn Enough Git to Be Dangerous》使用的是 GitHub,换用 Bitbucket 的原因参见旁注 1.4。)在 Bitbucket 中放一份 Git 仓库的副本有两个目的:其一,对代码做个完整备份(包括所有提交历史);其二,便于以后协作。

Bitbucket 的使用方法很简单,但毕竟“技术是复杂的”(旁注 1.1),因此可能也不是那么容易做对:

  1. 如果没有账户,先注册一个 Bitbucket 账户

  2. 把你的公钥复制到剪切板。云端 IDE 用户可以使用 cat 命令查看公钥,如代码清单 1.10 所示,然后选中公钥,复制。如果没有输出,或者遇到错误,请参阅“如何在你的 Bitbucket 账户中设定公钥”一文,然后再执行代码清单 1.10 中的命令;

  3. 点击右上角的头像,选择“Bitbucket settings”(Bitbucket 设置),然后点击“SSH keys”(SSH 密钥),如图 1.18 所示。

add public key
图 1.18:添加 SSH 公钥
代码清单 1.10:使用 cat 命令打印公钥
$ cat ~/.ssh/id_rsa.pub

添加公钥之后,点击“Create”(创建)按钮,新建一个仓库,如图 1.19 所示。填写项目的信息时,记得要选中“This is a private repository”(这是私有仓库)。填完后点击“Create repository”(创建仓库)按钮,然后按照说明操作,把仓库推送到 Bitbucket 中,如代码清单 1.11 所示。(如果与代码清单 1.11 不同,可能是公钥没正确添加,我建议你再试一次。)推送仓库时,如果询问“Are you sure you want to continue connecting (yes/no)?”(确定继续连接吗?),输入“yes”。

代码清单 1.11:添加 Bitbucket,然后推送仓库
$ git remote add origin git@bitbucket.org:<username>/hello_app.git
$ git push -u origin --all

代码清单 1.11 的意思是,先告诉 Git,你想添加 Bitbucket 作为这个仓库的源(origin),然后再把仓库推送到这个远端的源。(别管 -u 旗标的意思,如果好奇,可以搜索“git set upstream”。)当然了,你要把 <username> 换成你自己的用户名。例如,我执行的命令是:

$ git remote add origin git@bitbucket.org:railstutorial/hello_app.git

推送完毕后,在 Bitbucket 中会显示 hello_app 仓库的页面。在这个页面中可以浏览文件、查看完整的提交历史,除此之外还有很多其他功能(图 1.20)。[13]

create first repository bitbucket 4th ed
图 1.19:在 Bitbucket 中创建存放这个应用的仓库

1.4.4 分支、编辑、提交、合并

如果你跟着 1.4.3 节中的步骤做,可能注意到了,Bitbucket 自动渲染了仓库中的 README 文件,如图 1.20 所示。这个 README.md 文件由代码清单 1.3 中的命令自动生成。从文件的扩展名可以看出,这个文件使用 Markdown 编写。Markdown 是一门人类可读的标记语言,易于转换成 HTML——Bitbucket 就这么做了。

自动生成 README 文件很贴心,不过我们最好修改里面的内容,描述手上的项目。这一节,我们将修改 README 文件,添加一些针对本书的内容。在修改的过程中,我们将首次演示我推荐在 Git 中使用的工作流程,即“分支、编辑、提交、合并”。[14]

bitbucket repository page 4th ed
图 1.20:一个 Bitbucket 仓库的页面
bitbucket default readme
图 1.21:Bitbucket 渲染的 Rails 默认生成的 README 文件

分支

Git 分支(branch)的功能很强大。分支是对仓库的高效复制,在分支中所做的改动(或许是实验性质的)不会影响父级文件。大多数情况下,父级仓库是 master 分支。我们可以使用 checkout 命令,并指定 -b 旗标,创建一个新主题分支(topic branch):

$ git checkout -b modify-README
Switched to a new branch 'modify-README'
$ git branch
  master
* modify-README

其中,第二个命令 git branch 的作用是列出所有本地分支。星号(*)表示当前所在的分支。注意,git checkout -b modify-README 命令先创建一个新分支,然后再切换到这个新分支——modify-README 分支前面的星号证明了这一点。

只有多个开发者协作开发一个项目时,才能体现分支的全部价值。[15]如果只有一个开发者,分支也有作用。一般情况下,要把主题分支的改动和主分支隔离开,这样即便搞砸了,随时都可以切换到主分支,然后删除主题分支,丢掉改动。本节末尾会介绍具体做法。

顺便说一下,像这种小改动,我一般不会新建分支(而是直接在主分支中修改)。现在我这么做是为了让你养成好习惯。

编辑

创建好主题分支之后,我们要在 README 文件中添加一些内容,如代码清单 1.12 所示。

代码清单 1.12:新的 README 文件
README.md
# Ruby on Rails Tutorial

## "hello, world!"

This is the first application for the
[*Ruby on Rails Tutorial*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/). Hello, world!

提交

修改之后,查看一下该分支的状态:

$ git status
On branch modify-README
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

这里,我们本可以使用前面用过的 git add -A 命令,但是 git commit 命令提供了 -a 旗标,可以直接提交现有文件中的全部改动:

$ git commit -a -m "Improve the README file"
[modify-README 9dc4f64] Improve the README file
 1 file changed, 5 insertions(+), 22 deletions(-)

使用 -a 旗标一定要小心,千万别误用了。如果上次提交之后项目中添加了新文件,应该使用 git add -A,先告诉 Git 新增了文件。

注意,我们使用现在时(严格来说是祈使语气)编写提交消息。Git 把提交当做一系列补丁,在这种情况下,说明现在做了什么比说明过去做了什么要更合理。而且这种用法和 Git 命令生成的提交说明相配。详情参阅《Learn Enough Git to Be Dangerous》中的“Committing to Git”(提交到 Git 仓库)一节。

合并

我们已经改完了,现在可以把结果合并(merge)到主分支了:

$ git checkout master
Switched to branch 'master'
$ git merge modify-README
Updating af72946..9dc4f64
Fast-forward
 README.md | 27 +++++----------------------
 1 file changed, 5 insertions(+), 22 deletions(-)

注意,Git 命令的输出中经常会出现 34f06b7 这样的字符串,这是 Git 内部对仓库的指代。你得到的输出结果不会和我的一模一样,但大致相同。

合并之后,我们可以清理一下分支——如果主题分支不用了,可以使用 git branch -d 命令将其删除:

$ git branch -d modify-README
Deleted branch modify-README (was 9dc4f64).

这一步可做可不做,其实一般都会留着主题分支,这样就可以在两个分支之间来回切换,并在合适的时候把改动合并到主分支中。

前面提过,还可以使用 git branch -D 命令放弃主题分支中的改动:

# 仅作演示之用,如果没搞砸,千万别这么做
$ git checkout -b topic-branch
$ <really screw up the branch>
$ git add -A
$ git commit -a -m "Make major mistake"
$ git checkout master
$ git branch -D topic-branch

与旗标 -d 不同,如果指定旗标 -D,即使没合并分支中的改动,也会删除分支。

推送

我们已经更新了 README 文件,现在可以把改动推送到 Bitbucket,看看改动的效果。之前我们已经推送过一次(1.4.3 节),因此在大多数系统中都可以省略 origin master,直接执行 git push 命令:

$ git push

同样,Bitbucket 会把更新后的 Markdown 转换成 HTML(图 1.22)。

new readme bitbucket 4th ed
图 1.22:Bitbucket 中显示的更新后的 README 文件

1.5 部署

虽然现在只是第一章,我们还是要把(几乎没什么内容)的 Rails 应用部署到生产环境。这一步可做可不做,不过在开发过程中尽早且频繁地部署,可以尽早发现开发中的问题。在开发环境中花费大量精力之后再部署,往往会在发布时遇到严重的集成问题。[16]

以前,部署 Rails 应用是件痛苦的事。但最近几年,Rails 开发生态系统不断成熟,已经出现很多好的解决方案,例如使用 Phusion Passenger(Apache 和 Nginx [17] Web 服务器的一个模块)的共享主机和虚拟私有服务器,Engine YardRails Machine 这种提供全方位部署服务的公司,以及 Engine Yard CloudHeroku 这种云部署服务。

我最喜欢使用 Heroku 部署 Rails 应用。Heroku 专门用于部署 Rails 和其他 Web 应用,部署 Rails 应用的过程异常简单,只要源码纳入 Git 版本控制系统就好。(这也是为什么要按照 1.4 节介绍的步骤设置 Git。如果你还没有照着做,现在赶紧做吧。)很多情况下(包括这个教程),使用 Heroku 的免费套餐就可以了。

本节余下的内容专门介绍如何把我们的第一个应用部署到 Heroku 中。其中一些操作相对高级,如果没有完全理解也不要紧。本节的重点是把应用部署到线上环境中。

1.5.1 搭建 Heroku 部署环境

Heroku 使用 PostgreSQL[18] 数据库,因此我们要在生产环境安装 pg gem,这样 Rails 才能与 PostgreSQL 通信:[19]

group :production do
  gem 'pg', '0.20.0'
end

另外,要加入代码清单 1.5 所做的改动,避免在生产环境安装 sqlite3 gem,这是因为 Heroku 不支持 SQLite。

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

最终得到的 Gemfile 文件如代码清单 1.13 所示。

代码清单 1.13:增加并重新编排 gem 后的 Gemfile 文件
source 'https://rubygems.org'

gem 'rails',        '5.1.6'
gem 'puma',         '3.9.1'
gem 'sass-rails',   '5.0.6'
gem 'uglifier',     '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks',   '5.0.1'
gem 'jbuilder',     '2.7.0'

group :development, :test do
  gem 'sqlite3', '1.3.13'
  gem 'byebug',  '9.0.6', platform: :mri
end

group :development do
  gem 'web-console',           '3.5.1'
  gem 'listen',                '3.1.5'
  gem 'spring',                '2.0.2'
  gem 'spring-watcher-listen', '2.0.1'
end

group :production do
  gem 'pg', '0.20.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

为了准备好部署环境,下面要执行 bundle install 命令,并且指定一个特殊的选项,禁止在本地安装生产环境使用的 gem(这里指的是 pg),如代码清单 1.14 所示:

代码清单 1.14:不让 Bundler 安装生产环境的 gem
$ bundle install --without production

因为我们在代码清单 1.13 中只添加了用于生产环境的 gem,所以现在执行这个命令其实不会在本地安装任何新的 gem,但是又必须执行这个命令,因为我们要把 pg 添加到 Gemfile.lock 文件中。然后,提交这次改动:

$ git commit -a -m "Update Gemfile for Heroku"

接下来,我们要注册并配置一个 Heroku 新账户。首先,注册 Heroku 账户。然后,检查系统中是否已经安装 Heroku 命令行客户端:

$ heroku --version

如果有 heroku 命令行接口(command-line interface,CLI),会看到它的当前版本号。不过,在多数系统中,我们要自己动手安装 Heroku CLI。使用云端 IDE 的读者可以使用代码清单 1.15 中的命令安装。(这个命令相对高级,别深究细节。)

代码清单 1.15:在云端 IDE 中安装 Heroku CLI 的命令
$ source <(curl -sL https://cdn.learnenough.com/heroku_install)

上述命令执行完毕后,查看 Heroku CLI 的当前版本号,确认安装是否成功(得到的结果可能不同):

$ heroku --version
heroku-cli/6.15.5 (linux-x64) node-v9.2.1

确认 Heroku 命令行工具已经安装之后,使用 heroku 命令登录,然后添加 SSH 密钥:

$ heroku login
$ heroku keys:add

最后,执行 heroku create 命令,在 Heroku 的服务器中创建一个位置,用于存放演示应用,如代码清单 1.16 所示。

代码清单 1.16:在 Heroku 中创建一个新应用
$ heroku create
Creating app... done, fathomless-beyond-39164
https://damp-fortress-5769.herokuapp.com/ |
https://git.heroku.com/damp-fortress-5769.git

heroku 命令会为你的应用分配一个二级域名,立即生效。当然,现在还看不到内容,下面开始部署吧。

1.5.2 Heroku 部署第一步

部署应用的第一步是,使用 Git 把主分支推送到 Heroku 中:

$ git push heroku master

(你可能会看到一些提醒消息,现在先不管,7.5 节会解决。)

1.5.3 Heroku 部署第二步

其实没有第二步了。我们已经完成部署了。现在可以通过 heroku create 命令给出的地址(参见代码清单 1.16,如果没用云端 IDE,在本地可以执行 heroku open 命令)查看刚刚部署的应用,如图 1.23 所示。看到的页面和图 1.15 一样,但是现在这个应用运行在生产环境中。

heroku app hello world
图 1.23:运行在 Heroku 中的第一个应用
练习
  1. 前面的练习一样,想办法让生产环境中的应用显示“hola, mundo!”。

  2. 前面的练习一样,想办法修改根路由,显示 goodbye 动作渲染的结果。部署时,看看能不能省略 git push 命令中的 master,只使用 git push heroku

1.5.4 Heroku 命令

Heroku 命令行工具提供了很多命令,本节只简单介绍了几个。下面花几分钟再介绍一个命令,其作用是重命名应用:

$ heroku rename rails-tutorial-hello

你别再使用这个名字了,我已经占用了。或许,现在你无需做这一步,使用 Heroku 提供的默认地址就行。不过,如果你真想重命名应用,基于安全考虑,可以使用一些随机或难以猜测的二级域名,例如:

hwpcbmze.herokuapp.com
seyjhflo.herokuapp.com
jhyicevg.herokuapp.com

使用这样随机的二级域名,只有你将地址告诉别人他们才能访问你的网站。为了让你一窥 Ruby 的强大,下面是我用来生成随机二级域名的代码,很精妙吧。[20]

('a'..'z').to_a.shuffle[0..7].join

除了支持二级域名,Heroku 还支持自定义域名。(其实,本书的网站[21]就放在 Heroku 中。如果你阅读的是在线版,现在就在浏览一个托管于 Heroku 中的网站。)在 Heroku 文档中可以查看更多关于自定义域名的信息以及 Heroku 相关的其他话题。

练习
  1. 执行 heroku help 命令,查看 Heroku 命令列表。找到显示应用日志的命令。

  2. 使用前一题找到的命令查看应用的活动情况。应用刚刚发生了什么?(调试线上应用经常会用到这个命令。)

1.6 小结

这一章做了很多事:安装、搭建开发环境,版本控制以及部署。下一章会在这一章的基础上开发一个使用数据库的应用,让你看看 Rails 真正的本事。

如果此时你想分享阅读本书的进度,可以发一条推文或者更新 Facebook 状态,写上类似下面的内容:

我正在阅读《Ruby on Rails 教程》学习 Ruby on Rails!
http://railstutorial-china.org/

1.6.1 本章所学

  • Ruby on Rails 是一个使用 Ruby 编程语言开发的 Web 开发框架;

  • 在预先配置好的云端环境中安装 Rails、新建应用,以及编辑文件都很简单;

  • Rails 提供了命令行命令 rails,可用于新建应用(rails new)和启动本地服务器(rails server);

  • 添加了一个控制器动作,并且修改了根路由,最终开发出一个显示“hello, world!”的应用;

  • 为了避免丢失数据,也为了协作,我们把应用的源码纳入 Git 版本控制系统,而且还把最终得到的代码推送到 Bitbucket 中的一个私有仓库里;

  • 使用 Heroku 把应用部署到生产环境中。

  1. 版权归 Randall Munroe 所有,根据“知识共享 署名-非商业性使用 2.5 通用”许可证使用。译者做了汉化。
  2. URI 是“统一资源标识符”(Uniform Resource Identifier)的简称,较少使用的 URL 是“统一资源定位符”(Uniform Resource Locator)的简称。在实际使用中,URL 一般和浏览器地址栏中的内容一样。
  3. Cloud9 可能需要绑定信用卡才能使用,如果你不便绑定,可以换用其他在线 IDE。如果你想使用 Coding.net 的 WebIDE,可以参考这篇文章:http://railstutorial-china.org/setup/。——译者注
  4. 例如,要想找到 foo 函数的定义体,可以全局搜索“def foo”。
  5. AWS 发展很快,网站经常变化,细节跟这里所讲的可能不同。如有差异,请自行设法解决。
  6. 如果想进一步了解 printf,可以执行 man printf 命令。
  7. 这里便体现了“技术是复杂的”(旁注 1.1)。
  8. 表 3.1所示,可以省略 install,因为 bundlebundle install 的别名。
  9. 这里的“C”指代键盘上的字母,而不是大写的字母,所以不用按下 Shift 键输入大写的“C”。
  10. 本书的 Cloud9 分享 URL 的基 URL 由 rails-tutorial-c9-mhartl.c9.io 变成了 Amazon Web Services 的一个地址,但是很多情况下截图没变,因此某些插图中的浏览器地址栏里显示的还是以前的 URL(例如图 1.11)。这是一种细微差异,虽然体现了技术是复杂的(旁注 1.1),但是你应该能区分。
  11. 你的编辑器可能会显示一个消息,提示“invalid multibyte character”(无效的多字节字符),别去管它。如果你想让这个消息消失,可以在 Google 中搜索这个错误消息
  12. 本书基本不会修改这个文件,不过 3.6 节的高级测试配置中演示了如何修改。
  13. 因为我的公钥是在 Cloud9 中创建的,所以我以 railstutorial 的身份创建仓库,然后再把自己的主账户( mhartl)添加为协作者。这样,我便可以使用任何一个账户提交。
  14. 如果想在图形界面中操作 Git 仓库,可以使用 Atlassian 推出的 SourceTree 应用。
  15. 详情参阅《Learn Enough Git to Be Dangerous》书中“Collaborating”(协作)一节。
  16. 如果你担心不小心公开了应用,可以参照 1.5.4 节的做法。不过对本书开发的示例应用来说,这份担心是多余的。
  17. 读作“Engine X”。
  18. 读作 post-gres-cue-ell,通常简称 Postgres。
  19. 一般来说,开发环境和生产环境要尽量一致,例如使用相同的数据库。但在本书中,本地一直使用 SQLite,生产环境则使用 PostgreSQL。详情参见 3.1 节
  20. 其实这行代码还可以使用 Ruby 的一个内置方法进一步精简。这个方法是 sample('a'..'z').to_a.sample(8).join。感谢读者 Stefan Pochmann 指出这一点;在他告诉我之前,我甚至不知道有 sample 方法。
  21. 英文原版的网站托管在 Heroku 中,你现在阅读的中文版网站托管在 GitHub Pages 中。——译者注