1.3 第一个应用
最后更新于:2022-04-01 22:27:42
# 1.3 第一个应用
按照计算机编程领域[长期沿用的传统](http://www.catb.org/jargon/html/H/hello-world.html),第一个应用的目的是编写一个“hello, world”程序。具体说来,我们要创建一个简单的应用,在网页中显示字符串“hello, world!”,在开发环境([1.3.4 节](#hello-world))和线上网站中([1.5 节](#deploying))都是如此。
Rails 应用一般都从 `rails new` 命令开始,这个命令会在你指定的文件夹中创建一个 Rails 应用骨架。如果没使用[1.2.1 节](#development-environment)推荐的 Cloud9 IDE,首先你要新建一个文件夹,命名为 `workspace`,然后进入这个文件夹,如[代码清单 1.2](#listing-mkdir-rails-projects) 所示。([代码清单 1.2](#listing-mkdir-rails-projects) 中使用了 Unix 命令 `cd` 和 `mkdir`,如果你不熟悉这些命令,可以阅读[旁注 1.3](#aside-unix-commands)。)
##### 代码清单 1.2:为 Rails 项目新建一个文件夹,命名为 `workspace`(在云端环境中不用这一步)
```
$ cd # 进入家目录
$ mkdir workspace # 新建 workspace 文件夹
$ cd workspace/ # 进入 workspace 文件夹
```
##### 旁注 1.3:Unix 命令行速成课
使用 Windows 和 Mac OS X(数量较少,但增长势头迅猛)的用户可能对 Unix 命令行不熟悉。如果使用推荐的云端环境,很幸运,这个环境提供了 Unix(Linux)命令行——在标准的 [shell 命令行界面](http://en.wikipedia.org/wiki/Shell_(computing))中运行的 [Bash](http://en.wikipedia.org/wiki/Bash_(Unix_shell))。
命令行的基本思想很简单:使用简短的命令执行很多操作,例如创建文件夹(`mkdir`),移动和复制文件(`mv` 和 `cp`),以及变换目录浏览文件系统(`cd`)。主要使用图形化界面(Graphical User Interface,简称 GUI)的用户可能觉得命令行落后,其实是被表象蒙蔽了:命令行是开发者最强大的工具之一。其实,你经常会看到经验丰富的开发者开着多个终端窗口,运行命令行 shell。
这是一门很深的学问,但在本书中只会用到一些最常用的 Unix 命令行命令,如[表 1.1](#table-unix-commands)所示。若想更深入地学习 Unix 命令行,请阅读 Mark Bates 写的《[Conquering the Command Line](http://conqueringthecommandline.com/)》(可以[免费在线阅读](http://conqueringthecommandline.com/book),也可以[购买电子书和视频](http://conqueringthecommandline.com/#pricing))。
表 1.1:一些常用的 Unix 命令
| 作用 | 命令 | 示例 |
| --- | --- | --- |
| 列出内容 | `ls` | `$ ls -l` |
| 新建文件夹 | `mkdir <dirname>` | `$ mkdir workspace` |
| 变换目录 | `cd <dirname>` | `$ cd workspace/` |
| 进入上层目录 | `$ cd ..` |
| 进入家目录 | `$cd ~` 或 `$ cd` |
| 进入家目录中的文件夹 | `$ cd ~/workspace/` |
| 移动文件(重命名) | `mv <source> <target>` | `$ mv README.rdoc README.md` |
| 复制文件 | `cp <source> <target>` | `$ cp README.rdoc README.md` |
| 删除文件 | `rm <file>` | `$ rm README.rdoc` |
| 删除空文件夹 | `rmdir <directory>` | `$ rmdir workspace/` |
| 删除非空文件夹 | `rm -rf <directory>` | `$ rm -rf tmp/` |
| 连结并显示文件的内容 | `cat <file>` | `$ cat ~/.ssh/id_rsa.pub` |
不管在本地环境,还是在云端 IDE 中,下一步都是使用[代码清单 1.3](#listing-rails-command)中的命令创建第一个应用。注意,在这个代码清单中,我们明确指定了 Rails 版本(`4.2.2`)。这么做的目的是,确保使用[代码清单 1.1](#listing-installing-rails)中安装的 Rails 版本来创建这个应用的文件夹结构。(执行[代码清单 1.3](#listing-rails-command) 中的命令时,如果返回“Could not find ’railties”这样的错误,说明你没安装正确的 Rails 版本,再次确认你安装 Rails 时执行的命令和[代码清单 1.1](#listing-installing-rails) 一模一样。)
##### 代码清单 1.3:执行 `rails new` 命令(明确指定版本号)
```
$ cd ~/workspace $ rails _4.2.2_ new hello_app
create
create README.rdoc
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
create app/assets/javascripts/application.js
create app/assets/stylesheets/application.css
create app/controllers/application_controller.rb
.
.
.
create test/test_helper.rb
create tmp/cache
create tmp/cache/assets
create vendor/assets/javascripts
create vendor/assets/javascripts/.keep
create vendor/assets/stylesheets
create vendor/assets/stylesheets/.keep
run bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
Using rake 10.3.2
Using i18n 0.6.11
.
.
.
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 节](#bundler)说明这个命令的作用。
留意一下 `rails new` 命令创建的文件和文件夹。这个标准的文件夹结构(如[图 1.4](#fig-directory-structure-rails))是 Rails 的众多优势之一——让你从零开始快速的创建一个可运行的简单应用。而且,所有 Rails 应用都使用这种文件夹结构,所以阅读他人的代码时很快就能理清头绪。这些文件的作用如[表 1.2](#table-rails-directory-structure) 所示,在本书的后续内容中将介绍其中大多数文件和文件夹。从 [5.2.1 节](chapter5.html#the-asset-pipeline)开始,我们将介绍 `app/assets` 文件夹,它是 Asset Pipeline 的一部分。Asset Pipeline 把组织、部署 CSS 和 JavaScript 等资源文件变得异常简单。
![directory structure rails 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcaad02dd.png)图 1.4:新建 Rails 应用的文件夹结构
表 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.rdoc` | 应用简介 |
| `Rakefile` | 使用 `rake` 命令执行的实用任务 |
| `Gemfile` | 应用所需的 gem |
| `Gemfile.lock` | gem 列表,确保这个应用的副本使用相同版本的 gem |
| `config.ru` | [Rack 中间件](http://rack.github.io/)的配置文件 |
| `.gitignore` | Git 忽略的文件 |
## 1.3.1 Bundler
创建完一个新的 Rails 应用后,下一步是使用 Bundler 安装和包含该应用所需的 gem。在 [1.3 节](#the-first-application)简单提到过,执行 `rails new` 命令时会自动运行 Bundler(通过 `bundle install` 命令)。不过这一节,我们要修改应用默认使用的 gem,然后再次运行 Bundler。首先,在文本编辑器中打开文件 `Gemfile`,虽然具体的版本号和内容或许有所不同,但大概与[代码清单 1.4](#listing-default-gemfile) 和[图 1.5](#fig-cloud9-gemfile) 差不多。(这个文件中的内容是 Ruby 代码,现在先不关心句法,[第 4 章](chapter4.html#rails-flavored-ruby)会详细介绍 Ruby。)如果你没看到如图 1.5 所示的文件和文件夹,点击文件浏览器中的齿轮图标,然后选择“Refresh File Tree”(刷新文件树)。(如果没出现某个文件或文件夹,就可以刷新文件树。)
##### 代码清单 1.4:`hello_app` 中默认生成的 `Gemfile`
```
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.2'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.1.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more:
# https://github.com/rails/turbolinks
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Unicorn as the app server
# gem 'unicorn'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
group :development, :test do
# Call 'debugger' anywhere in the code to stop execution and get a
# debugger console
gem 'byebug'
# Access an IRB console on exceptions page and /console in development
gem 'web-console', '~> 2.0'
# Spring speeds up development by keeping your application running in the
# background. Read more: https://github.com/rails/spring
gem 'spring'
end
```
![cloud9 gemfile](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcaaee251.png)图 1.5:在文本编辑器中打开默认生成的 `Gemfile`
其中很多行代码都用 `#` 符号注释掉了,这些代码放在这是为了告诉你一些常用的 gem,也是为了展示 Bundler 的句法。现在,除了这些默认的 gem 之外,我们还不需要其他的 gem。
如果没在 `gem` 指令中指定版本号,Bundler 会自动最新版。下面就是一例:
```
gem 'sqlite3'
```
还有两种常用的方法,用来指定 gem 版本的范围,一定程度上控制 Rails 使用的版本。首先看下面这行代码:
```
gem 'uglifier', '>= 1.3.0'
```
这行代码的意思是,安装版本号大于或等于 `1.3.0` 的最新版 `uglifier`(作用是压缩 Asset Pipeline 中的文件),就算是 `7.2` 版也会安装。第二种方法如下所示:
```
gem 'coffee-rails', '~> 4.0.0'
```
这行代码的意思是,安装版本号大于 `4.0.0`,但小于 `4.1` 的 `coffee-rails`。也就是说,`>=` 符号的意思是始终安装最新版;`~> 4.0.0` 的意思是只安装补丁版本号变化的版本(例如从 `4.0.0` 到 `4.0.1`),而不安装次版本或主版本的更新(例如从 `4.0` 到 `4.1`)。不过,经验告诉我们,即使是补丁版本的升级也可能导致错误,所以在本教程中我们基本上会为所有的 gem 都指定精确的版本号。你可以使用任何 gem 的最新版本,还可以在 `Gemfile` 中使用 `~>`(一般推荐有经验的用户使用),但事先提醒你,这可能会导致本教程开发的应用表现异常。
修改[代码清单 1.4](#listing-default-gemfile) 中的 `Gemfile`,换用精确的版本号,得到的结果如[代码清单 1.5](#listing-gemfile-sqlite-version) 所示。注意,借此机会我们还变动了 `sqlite3` 的位置,只在开发环境和测试环境([7.1.1 节](chapter7.html#debug-and-rails-environments))中安装,避免和 Heroku 所用的数据库冲突([1.5 节](#deploying))。
##### 代码清单 1.5:每个 Ruby gem 都使用精确版本号的 `Gemfile`
```
source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'sass-rails', '5.0.2'
gem 'uglifier', '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks', '2.3.0'
gem 'jbuilder', '2.2.3'
gem 'sdoc', '0.4.0', group: :doc
group :development, :test do
gem 'sqlite3', '1.3.9'
gem 'byebug', '3.4.0'
gem 'web-console', '2.0.0.beta3'
gem 'spring', '1.1.3'
end
```
把代码清单 1.5 中的内容写入应用的 `Gemfile` 文件之后,执行 `bundle install` 命令[[7](#fn-7)]安装这些 gem:
```
$ cd hello_app/ $ bundle install Fetching source index for https://rubygems.org/
.
.
.
```
`bundle install` 命令可能要执行一会儿,不过结束后我们的应用就可以运行了。
## 1.3.2 `rails server`
运行完 [1.3 节](#the-first-application)中的 `rails new` 命令和 [1.3.1 节](#bundler) 中的 `bundle install` 命令之后,我们的应用就可以运行了,但是怎么运行呢?Rails 自带了一个命令行程序(或叫脚本),可以运行一个本地服务器,协助我们的开发工作。这个命令具体怎么执行,取决于你使用的环境:在本地系统中,直接执行 `rails server` 命令就行([代码清单 1.6](#listing-local-server));而在 Cloud9 中,还要指定绑定的 IP 地址和[端口号](http://en.wikipedia.org/wiki/TCP_and_UDP_port),告诉 Rails 服务器外界可以通过哪个地址访问应用([代码清单 1.7](#listing-cloud-server))。[[8](#fn-8)](Cloud9 使用特殊的环境变量 `$IP` 和 `$PORT` 动态指定 IP 地址和端口号。如果想查看这两个环境变量的值,可以在命令行中输入 `echo $IP` 和 `echo $PORT`。)如果系统提示缺少 JavaScript 运行时,访问 [execjs 在 GitHub 中的项目主页](https://github.com/sstephenson/execjs),查看解决方法。我非常推荐安装 [Node.js](http://nodejs.org/)。
##### 代码清单 1.6:在本地设备中运行 Rails 服务器
```
$ cd ~/workspace/hello_app/ $ rails server => Booting WEBrick
=> Rails application starting on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
```
##### 代码清单 1.7:在云端 IDE 中运行 Rails 服务器
```
$ cd ~/workspace/hello_app/ $ rails server -b $IP -p $PORT => Booting WEBrick
=> Rails application starting on http://0.0.0.0:8080
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
```
不管使用哪种环境,我都建议你在另一个终端选项卡中执行 `rails server` 命令,这样你就可以继续在第一个选项卡中执行其他命令了,如[图 1.6](#fig-new-terminal-tab) 和[图 1.7](#fig-rails-server-new-tab) 所示。(如果你已经在第一个选项卡中启动了服务器,可以按 Ctrl-C 键关闭服务器。)在本地环境中,在浏览器中打开 [http://localhost:3000/](http://localhost:3000/);在云端 IDE 中,打开“Share”(分享)面板,点击“Application”后的地址即可打开应用(如[图 1.8](#fig-share-workspace))。在这两种环境中,显示的页面应该都和[图 1.9](#fig-riding-rails) 类似。
点击“About your application’s environment”可以查看应用的信息。你看到的版本号可能和我的不一样,但和[图 1.10](#fig-riding-rails-environment) 差不多。当然了,从长远来看,我们不需要这个 Rails 默认页面,不过现在看到这个页面说明 Rails 可以正常运行了。我们会在[1.3.4 节](#hello-world)删除这个页面,替换成我们自己写的首页。
![new terminal tab](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcab1e6e8.png)图 1.6:再打开一个终端选项卡![rails server new tab](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcab41dd1.png)图 1.7:在另一个选项卡中运行 Rails 服务器![share workspace](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcab66a18.png)图 1.8:分享运行在云端工作空间中的本地服务器![riding rails 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcab82886.png)图 1.9:执行 `rails server` 命令后看到的 Rails 默认页面![riding rails environment 3rd edition](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcab9fb7d.png)图 1.10:默认页面中显示应用的环境信息
## 1.3.3 模型-视图-控制器
在初期阶段,概览一下 Rails 应用的工作方式([图 1.11](#fig-mvc))多少会有些帮助。你可能已经注意到了,在 Rails 应用的标准文件夹结构中有一个文件夹叫 `app/`([图 1.4](#fig-directory-structure-rails)),其中有三个子文件夹:`models`、`views` 和 `controllers`。这暗示 Rails 采用了“模型-视图-控制器”(简称 MVC)架构模式,这种模式把“域逻辑”(domain logic,也叫“业务逻辑”(business logic))与图形用户界面相关的输入和表现逻辑强制分开。在 Web 应用中,“域逻辑”一般是“用户”、“文章”和“商品”等数据模型,GUI 则是浏览器中的网页。
![mvc schematic](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcabbbad3.png)图 1.11:MVC 架构图解
和 Rails 应用交互时,浏览器发出一个请求(request),Web 服务器收到这个请求之后将其传给 Rails 应用的控制器,由控制器决定下一步该做什么。某些情况下,控制器会立即渲染视图(view),生成 HTML,然后发送给浏览器。对于动态网站来说,更常见的是控制器和模型(model)交互。模型是一个 Ruby 对象,表示网站中的一个元素(例如一个用户),并且负责和数据库通信。和模型交互后,控制器再渲染视图,并把生成的 HTML 返回给浏览器。
如果你觉得这些内容有点抽象,不用担心,后面会经常讲到 MVC。在[1.3.4 节](#hello-world)中,首次使用 MVC 架构编写应用;在 [2.2.2 节](chapter2.html#mvc-in-action)中,会以一个应用为例较为深入地讨论 MVC;在最后那个演示应用中会使用完整的 MVC 架构。从 [3.2 节](chapter3.html#static-pages)开始,介绍控制器和视图;从 [6.1 节](chapter6.html#user-model)开始,介绍模型;[7.1.2 节](chapter7.html#a-users-resource)则把这三部分放在一起使用。
## 1.3.4 Hello, world!
接下来我们要对这个使用 MVC 框架开发的第一个应用做些小改动——添加一个控制器动作,渲染字符串“hello, world!”。(从 [2.2.2 节](chapter2.html#mvc-in-action)开始会更深入的介绍控制器动作。)这一节的目的是,使用显示“hello, world!”的页面替换 Rails 默认的首页([图 1.9](#fig-riding-rails))。
从“控制器动作”这个名字可以看出,动作在控制器中定义。我们要在 `ApplicationController` 中定义这个动作,并将其命名为 `hello`。其实,现在我们的应用只有 `ApplicationController` 这一个控制器。执行下面的命令可以验证这一点(从 [第 2 章](chapter2.html#a-toy-app)开始,我们会创建自己的控制器。):
```
$ ls app/controllers/*_controller.rb
```
`hello` 动作的定义体如[代码清单 1.8](#listing-hello-action) 所示,调用 `render` 函数返回文本“hello, world!”。(现在先不管 Ruby 的句法,[第 4 章](chapter4.html#rails-flavored-ruby)会详细介绍。)
##### 代码清单 1.8:在 `ApplicationController` 中添加 `hello` 动作
app/controllers/application_controller.rb
```
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def hello
render text: "hello, world!" end
end
```
定义好返回所需字符串的动作之后,我们要告诉 Rails 使用这个动作,不再显示默认的首页([图 1.10](#fig-riding-rails-environment))。为此,我们要修改 Rails 路由。路由在控制器之前([图 1.11](#fig-mvc)),决定浏览器发给应用的请求由哪个动作处理。(简单起见,[图 1.11](#fig-mvc) 中省略了路由,从 [2.2.2 节](chapter2.html#mvc-in-action)开始会详细介绍路由。)具体而言,我们要修改默认的首页,也就是根路由。这个路由决定根 URL 显示哪个页面。根 URL 是 http://www.example.com/ 这种形式,所以一般简化使用 /(斜线)表示。
如[代码清单 1.9](#listing-default-root-route) 所示,Rails 应用的路由文件(`config/routes.rb`)中有一行注释,说明如何编写根路由。其中,“welcome”是控制器名,“index”是这个控制器中的动作名。去掉这行前面的 `#` 号,解除注释,这样根路由就可以定义了,然后再把内容替换成[代码清单 1.10](#listing-hello-root-route) 中的代码,告诉 Rails 把根路由交给 `ApplicationController` 中的 `hello` 动作处理。(在 [1.1.2 节](#conventions-in-this-book)说过,竖排的点号表示省略的代码,不要直接复制。)
##### 代码清单 1.9:生成的默认根路由(在注释中)
config/routes.rb
```
Rails.application.routes.draw do
.
.
.
# You can have the root of your site routed with "root"
# root 'welcome#index' .
.
.
end
```
##### 代码清单 1.10:设定根路由
config/routes.rb
```
Rails.application.routes.draw do
.
.
.
# You can have the root of your site routed with "root"
root 'application#hello' .
.
.
end
```
有了[代码清单 1.8](#listing-hello-action) 和[代码清单 1.10](#listing-hello-root-route) 中的代码,根路由就会按照我们的要求显示“hello, world!”了,如[图 1.12](#fig-hello-world-hello-app) 所示。
![hello world hello app](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcabcf659.png)图 1.12:在浏览器中查看显示“hello, world!”的页面
';