Rails起步走

Rails 起步走

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies. - C.A.R. Hoare,

在这一章中,我们将开始介绍如何建立一个最简单的 Hello, World! 程式,以及用最快速的方式制作 CRUD 应用。

CRUD 指的是 Create(新增)、 Read(读取)、 Update(更新)、 Delete(删除)四种操作资料的基本方式。

在上一章安装 Rails 后,你会在命令列中得到一个rails的指令,这个指令可以初始一个 Rails 专案目录。

开始建立第一个Rails应用程式

首先请打开一个命令列视窗 (Terminal) ,然后找个目录适合放你的 Rails 专案,就说是projects好了:

$ mkdir projects
$ cd projects

接着,输入以下指令就会建立一个叫做 demo 的 Rails 专案:

$ rails new demo --skip-test-unit

如果出现建立出来的目录不是 demo 而是 new ,表示你的 Rails 版本是旧版的,请输入rails -v检查 Rails 的版本必须是 3.0 以上。不是的话,请回上一章末执行gem install rails安装 Rails 4 。

你会看到以下讯息显示出总共新增了哪些档案:

create
create  README
create  Rakefile
create  config.ru
create  .gitignore
create  Gemfile
create  app
...(略)...
create  vendor/plugins
create  vendor/plugins/.gitkeep

这样就建立出 demo 目录,让我们继续,进入刚产生出来的 demo 目录下:

$ cd demo

这个目录下包含了一个 Rails 专案基本会用到的目录结构和档案,让我们简单走访一下,输入ls (Windows读者请输入dir) 显示出此目录下的档案:

档案/目录 用途
Gemfile 设定Rails应用程式会使用哪些Gems套件
README 专案说明:你可以用来告诉其他人你的应用程式是做什么用的,如何使用等等。
Rakefile 用来加载可以被命令列执行的一些Rake任务
app/ 放Controllers、Models和Views档案,接下来的内容主要都在这个目录。
config/ 应用程式设定档、路由规则、数据库设定等等
config.ru 用来启动应用程式的Rack服务器设定档
db/ 数据库的结构纲要
doc/ 用来放你的文件
lib/ 放一些自定的Module和类别档案
log/ 应用程式的Log记录档
public/ 唯一可以在网络上看到的目录,这是你的图档、JavaScript、CSS和其他静态档案摆放的地方
bin/ 放rails这个指令和放其他的script指令
test/ 单元测试、fixtures及整合测试等程式
tmp/ 暂时性的档案
vendor/ 用来放第三方程式码外挂的目录

启动服务器

Rails 使用了一套叫做 Bundler 的工具可以帮助我们检查及安装这个 Rails 应用程式所有依存的套件,在 demo 目录下输入:

$ bundle install

可以只输入bundle就是bundle install了。每次有修改 Gemfile 这个档案,都需要重新执行bundle

会出现

Fetching source index for http://rubygems.org/
...
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

在开发用的电脑上,我们不需要安装如 Apache 、 IIS 的网站服务器。 Ruby 本身就有提供了 HTTP 服务器可以执行 Rails ,要启动它,我们另开启一个指令视窗, cd 进到刚刚建立的 Rails 专案目录然后输入 bin/rails server :

$ cd projects/demo
$ bin/rails server

Windows 作业系统请输入ruby bin/rails server

就会出现以下讯息:

=> Booting Puma
=> Rails 5.1.0.beta1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.7.1 (ruby 2.4.0-p0), codename: Snowy Sagebrush
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

rails server 可以简写为 rails s

使用 Ubuntu 作业系统的朋友,如果启动服务器时出现 Could not find a JavaScript runtime 的错误,请编辑 Gemfile 这个档案加上一行gem 'therubyracer',输入bundle install安装这个套件,然后再启动一次rails server即可。这是因为在 Ubuntu 作业系统上默认没有任何 JavaScript 直译器可以给 Rails 使用。你可以装 Node.js 或是安装 therubyracer 这个 Ruby 套件来获得 JavaScript 直译器。

接着打开你的浏览器前往 <http://localhost:3000> ,我们可以看到 Rails 的默认首页。这个 Welcome Aboard 的画面可以确认设定无误,点选 About your application’s environment 超连结可以看到更多环境资讯。

Welcome screenshot

要中断服务器的话,请按 Ctrl+C(若不灵光请改试 Ctrl+Z)。在 development 开发模式的话,除了修改 config 或 vender 目录下的档案需要重新启动之外,其他修改通常不需要重新启动,修改的档案会自动重新加载。如果是 production 正式上线模式的话,修改任何档案都必须重新启动服务器才会有效果。

第一个Hello World!!

让程式说 Hello World! 可是我们学写程式的一大传统。我们提过 Rails 是 MVC 框架,显示 Hello World! 不需要用到数据库,所以我们只要先写 Controller 和 View ,以及让路由指派到这个 Controller 即可,输入以下指令就会产生出一个叫做 welcome 的空 Controller 档案:

$ bin/rails generate controller welcome

可以简写为bin/rails g controller welcome

接下来在路由档案 config/routes.rb 新增一行设定:

Rails.application.routes.draw do
  get "welcome/say_hello" => "welcome#say"
  # ...
end

get这一行的意思是将<http://localhost:3000/welcome/say_hello>这样的网址对应到 welcome Controller 的 say Action 。

编辑 app/controllers/welcome_controller.rb ,加入一个say方法:

class WelcomeController < ApplicationController
  def say
  end
end

在 Controller 中,一个公开函式 (public method) 就代表一个 Action ,一个 Action 对应一个 HTTP 的请求和回应。接着我们打开浏览器浏览 <http://localhost:3000/welcome/say_hello> ,你会看到一个错误如下:

Missing template welcome/say, application/say with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}. Searched in: * "/Users/ihower/projects/demo/app/views"

这是因为我们还没有准备好 View 档案。请新增 app/views/welcome/say.html.erb 这个档案,依照惯例目录名就是 Controller 名称、档案名是 Action 名称,第一个附档名说明了这是 HTML 格式的档案,第二个附档名说明这是 ERb 样板(我们会在 View 一章仔细介绍样板)。编辑该档案内容如下:

<h1>Hello, World!</h1>

这时再重新整理一次浏览器,你就会看到 Hello, World! 了。

让我们再新增一个页面并加入超连结。再次编辑路由档案 config/routes.rb 加入一个路由,变成这样:

Rails.application.routes.draw do
  get "welcome/say_hello" => "welcome#say"
  get "welcome" => "welcome#index"
  # ...
end

这一行的意思是将<http://localhost:3000/welcome>这样的网址对应到 welcome Controller 的 index Action 。编辑 app/controllers/welcome_controller.rb 加入

class WelcomeController < ApplicationController

  #...

  def index
  end
end

新增 app/views/welcome/index.html.erb 内容是

<p>Hola! It's <%= Time.now %></p>
<p><%= link_to 'Hello!', welcome_say_hello_path %></p>

Time是 Ruby 内建的时间类别,Time.now会输出目前时间。link _to_ 是_Rails 内建的方法可以输出超连结,而 welcome_say_hello_path 会输出/welcome/say _hello_ 这个网址。这种出现在_View 中的辅助方法统称作 Helper 。浏览 <http://localhost:3000/welcome> ,将看到 Hola! 及 Hello! 超连结。

Hello screenshot

设定首页

如何将网站首页变更为 welcome#index 呢?编辑 config/routes.rb ,加上以下的程式码,变成这样:

Rails.application.routes.draw do
  get "welcome/say_hello" => "welcome#say"
  get "welcome" => "welcome#index"

  root :to => "welcome#index"
  # ...
end

Ruby 的单行注解是用#井号

这一行的意思是,将网站根目录导引至 welcome Controller 的 index Action 。那在 View 中要怎么建立回首页的连结呢?编辑 app/views/welcome/say.html.erb 在Hello, World!下一行加入:

<h1>Hello, World!</h1>
<p><%= link_to "Home", root_path %></p>

如此一来,网页的首页就会显示Hola!和时间,连到 <http://localhost:3000/welcome/say_hello> 的时候也会在底下显示一个「Home」的连结,点下去就会回到首页了。

设定及建立数据库

操作数据库是动态网站非常基本的功能,在撰写 CRUD 应用程式之前,我们必须先设定好数据库。 Rails 的数据库设定档是 config/database.yml ,如果你打开这个档案,默认的设定是 SQLite3 。这个档案里包含三段不同环境的设定,对应到三个 Rails 执行环境:

  • development 开发模式,用在你的开发的时候
  • test 测试模式,用在执行自动测试时
  • production 正式上线模式,用在实际的上线运作环境

设定SQLite数据库

Rails 内建支援 SQLite 这是一套非常轻量的非服务器型数据库程式,它的数据库就只是一个档案而已。流量大的正式上线环境虽然不适合 SQLite ,不过拿来开发和测试却非常好用。 Rails 默认也使用 SQLite 数据库来建立新的专案,以下是默认的设定资料 config/database.yml :

## SQLite version 3.x
##   gem install sqlite3
#
##   Ensure the SQLite 3 gem is defined in your Gemfile
##   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

## Warning: The database defined as "test" will be erased and
## re-generated from your development database when you run "rake".
## Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

中间那段注解告诉你不要把 test 数据库设成跟 production 或 development 同一个

本书接下来也都使用 SQLite 数据库,因为它完全不需要什么设定就可以使用。

YAML 是一种可读性高,用来表达设定资料的资料格式。它严格要求缩排(建议为两个空白),且冒号后面必须有一个空格。一般我们会预期 YAML 的值解析出来是字串,因此如果内容是数字或多行文字时,建议加上引号以避免字串解析错误。例如 password: "123456"。如果没有加上引号,这串数字会被解析成 Fixnum 物件而不是字串 String ,后续可能造成型别判断错误。

建立数据库

数据库设定好了,输入以下的指令可以让 Rails 建立出空的数据库:

$ bin/rake db:create

这将在 db/ 目录下建立出 development.sqlite3 这个数据库档案。

Rake 是一种 Ruby 的命令列工具,你可以输入rake -T列出所有可用的指令。我们会在稍后的章节中详细介绍 Rake 。

你的第一个CRUD程式

Rails 的 scaffold 鹰架功能会自动产生一组 Model 、 Views 跟 Controller 程式码,完成一个简易的 CRUD 程式以供展示及学习之用。请输入:

$ bin/rails g scaffold person name:string bio:text birthday:date

产生的档案简单说明如下,请注意 Model 的名称是用单数 person ,而 Controller 照 RESTful 惯例是用复数 people :

db/migrate/20141021135430_create_people.rb 用来建立people数据库资料表的Migration(你的档案开头名称会有不同的时间)
app/models/person.rb person model档案
app/controllers/people_controller.rb people controller档案
app/views/people/index.html.erb 用来显示所有文章的index页面
app/views/people/edit.html.erb 用来编辑文章的页面
app/views/people/show.html.erb 用来显示特定一篇文章的页面
app/views/people/new.html.erb 用来新增文章的页面
app/views/people/_form.html.erb 用来显示编辑和新增文章的表单局部(Partial)样板
app/helpers/people_helper.rb 可在文章Views中使用的Helper方法
config/routes.rb 设定URL路由规则的档案,scaffold再此新增了一行resources :people
app/assets/stylesheets/scaffold.scss Scaffold鹰架提供的样式档案
app/assets/stylesheets/people.scss people的CSS样式档案
app/assets/javascripts/people.coffee people的JavaScript档案

虽然鹰架 (scaffolding) 可以帮助你快速上手,但是可没办法产生出完美符合需求的程式码。因此有经验的 Rails 程式设计师甚少使用默认的鹰架产生程式码,而是偏好使用 Rails 的 generator 来分别产生 Model 和 Controller 档案,甚至客制出自己专属的 scaffold 程式。

scaffold 产生出来的程式中,有一项是数据库迁移档 (database migration) 。 Migration 的用途是建立和修改数据库资料表。 Rails 使用 rake 指令来执行 Migrations 。 Migration 的档名中包含了 Timestamp(时间戳章),用来确保它们可以依照建立时间依序执行。

请输入以下指令执行 Migration :

$ bin/rake db:migrate

Rails 这时会建立 people 资料表:

==  Createpeople: migrating ====================================================
-- create_table(:people)
   -> 0.0019s
==  Createpeople: migrated (0.0020s) ===========================================

因为默认是跑在 development 模式,这个指令会用 config/database.yml 设定里的 development 那段所指定的数据库。

此时浏览 <http://localhost:3000/people> 就可以操作了,十分神奇吧!不过,这里就不详细说明其产生出来的程式码了,读者读毕稍后章节后,自会明白。

Scaffold screenshot

常见错误

NoMethodError

NoMethodError 非常明显,就是你打错方法名称了,例如此例中把link_to打成link_too。根据错误讯息你应该可以很容易找到错误是发生在哪个档案、哪一行。

NameError

读取一个不存在、没有初始过的区域变量会出现 NameError 的错误

SyntaxError: unexpected $end

SyntaxError 加上 unexpected $end, expecting keyword_end 的话,那一定是你少了(或多了)end关键字,def跟do都必须要有对应的end。不过很可惜 Rails 没办法提示你是那一行少了(或多了)end,发生错误的行数都会告诉你是最后一行。如果真的不太好找,你可以单独用ruby -w去执行发生错误的程式,例如ruby -w app/controller/welcome _controller_ ,这会打开_Ruby 的警告模式来获得更准确的语法错误讯息。