一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Rails 入門

 昵稱27265735 2017-04-26

1 前提條件

本文針對想從零開始開發(fā) Rails 應(yīng)用的初學(xué)者,不要求 Rails 使用經(jīng)驗(yàn)。不過,為了能順利閱讀,還是需要事先安裝好一些軟件:

Rails 是使用 Ruby 語言開發(fā)的 Web 應(yīng)用框架。如果之前沒接觸過 Ruby,會(huì)感到直接學(xué)習(xí) Rails 的學(xué)習(xí)曲線很陡。這里提供幾個(gè)學(xué)習(xí) Ruby 的在線資源:

需要注意的是,有些資源雖然很好,但針對的是 Ruby 1.8 甚至 1.6 這些老版本,因此不涉及一些 Rails 日常開發(fā)的常見句法。

2 Rails 是什么?

Rails 是使用 Ruby 語言編寫的 Web 應(yīng)用開發(fā)框架,目的是通過解決快速開發(fā)中的共通問題,簡化 Web 應(yīng)用的開發(fā)。與其他編程語言和框架相比,使用 Rails 只需編寫更少代碼就能實(shí)現(xiàn)更多功能。有經(jīng)驗(yàn)的 Rails 程序員常說,Rails 讓 Web 應(yīng)用開發(fā)變得更有趣。

Rails 有自己的設(shè)計(jì)原則,認(rèn)為問題總有最好的解決方法,并且有意識地通過設(shè)計(jì)來鼓勵(lì)用戶使用最好的解決方法,而不是其他替代方案。一旦掌握了“Rails 之道”,就可能獲得生產(chǎn)力的巨大提升。在 Rails 開發(fā)中,如果不改變使用其他編程語言時(shí)養(yǎng)成的習(xí)慣,總想使用原有的設(shè)計(jì)模式,開發(fā)體驗(yàn)可能就不那么讓人愉快了。

Rails 哲學(xué)包含兩大指導(dǎo)思想:

  • 不要自我重復(fù)(DRY): DRY 是軟件開發(fā)中的一個(gè)原則,意思是“系統(tǒng)中的每個(gè)功能都要具有單一、準(zhǔn)確、可信的實(shí)現(xiàn)?!?。不重復(fù)表述同一件事,寫出的代碼才更易維護(hù)、更具擴(kuò)展性,也更不容易出問題。

  • 多約定,少配置: Rails 為 Web 應(yīng)用的大多數(shù)需求都提供了最好的解決方法,并且默認(rèn)使用這些約定,而不是在長長的配置文件中設(shè)置每個(gè)細(xì)節(jié)。

3 創(chuàng)建 Rails 項(xiàng)目

閱讀本文的最佳方法是一步步跟著操作。所有這些步驟對于運(yùn)行示例應(yīng)用都是必不可少的,同時(shí)也不需要更多的代碼或步驟。

通過學(xué)習(xí)本文,你將學(xué)會(huì)如何創(chuàng)建一個(gè)名為 Blog 的 Rails 項(xiàng)目,這是一個(gè)非常簡單的博客。在動(dòng)手開發(fā)之前,請確保已經(jīng)安裝了 Rails。

文中的示例代碼使用 UNIX 風(fēng)格的命令行提示符 $,如果你的命令行提示符是自定義的,看起來可能會(huì)不一樣。在 Windows 中,命令行提示符可能類似 c:\source_code>

3.1 安裝 Rails

打開命令行:在 Mac OS X 中打開 Terminal.app,在 Windows 中要在開始菜單中選擇“運(yùn)行”,然后輸入“cmd.exe”。本文中所有以 $ 開頭的代碼,都應(yīng)該在命令行中執(zhí)行。首先確認(rèn)是否安裝了 Ruby 的最新版本:

$ ruby -v
ruby 2.3.0p0

有很多工具可以幫助你快速地在系統(tǒng)中安裝 Ruby 和 Ruby on Rails。Windows 用戶可以使用 Rails Installer,Mac OS X 用戶可以使用 Tokaido。更多操作系統(tǒng)中的安裝方法請?jiān)L問

很多類 UNIX 系統(tǒng)都預(yù)裝了版本較新的 SQLite3。在 Windows 中,通過 Rails Installer 安裝 Rails 會(huì)同時(shí)安裝 SQLite3。其他操作系統(tǒng)中 SQLite3 的安裝方法請參閱 SQLite3 官網(wǎng)。接下來,確認(rèn) SQLite3 是否在 PATH 中:

$ sqlite3 --version

執(zhí)行結(jié)果應(yīng)該顯示 SQLite3 的版本號。

安裝 Rails,請使用 RubyGems 提供的 gem install 命令:

$ gem install rails

執(zhí)行下面的命令來確認(rèn)所有軟件是否都已正確安裝:

$ rails --version

如果執(zhí)行結(jié)果類似 Rails 5.0.0,那么就可以繼續(xù)往下讀了。

3.2 創(chuàng)建 Blog 應(yīng)用

Rails 提供了許多名為“生成器”(generator)的腳本,這些腳本可以為特定任務(wù)生成所需的全部文件,從而簡化開發(fā)。其中包括新應(yīng)用生成器,這個(gè)腳本用于創(chuàng)建 Rails 應(yīng)用骨架,避免了手動(dòng)編寫基礎(chǔ)代碼。

要使用新應(yīng)用生成器,請打開終端,進(jìn)入具有寫權(quán)限的文件夾,輸入:

$ rails new blog

這個(gè)命令會(huì)在文件夾 blog 中創(chuàng)建名為 Blog 的 Rails 應(yīng)用,然后執(zhí)行 bundle install 命令安裝 Gemfile 中列出的 gem 及其依賴。

執(zhí)行 rails new -h 命令可以查看新應(yīng)用生成器的所有命令行選項(xiàng)。

創(chuàng)建 blog 應(yīng)用后,進(jìn)入該文件夾:

$ cd blog

blog 文件夾中有許多自動(dòng)生成的文件和文件夾,這些文件和文件夾組成了 Rails 應(yīng)用的結(jié)構(gòu)。本文涉及的大部分工作都在 app 文件夾中完成。下面簡單介紹一下這些用新應(yīng)用生成器默認(rèn)選項(xiàng)生成的文件和文件夾的功能:

文件/文件夾 作用
app/ 包含應(yīng)用的控制器、模型、視圖、輔助方法、郵件程序和靜態(tài)資源文件。這個(gè)文件夾是本文剩余內(nèi)容關(guān)注的重點(diǎn)。
bin/ 包含用于啟動(dòng)應(yīng)用的 rails 腳本,以及用于安裝、更新、部署或運(yùn)行應(yīng)用的其他腳本。
config/ 配置應(yīng)用的路由、數(shù)據(jù)庫等。詳情請參閱configuring.xml。
config.ru 基于 Rack 的服務(wù)器所需的 Rack 配置,用于啟動(dòng)應(yīng)用。
db/ 包含當(dāng)前數(shù)據(jù)庫的模式,以及數(shù)據(jù)庫遷移文件。
Gemfile, Gemfile.lock 這兩個(gè)文件用于指定 Rails 應(yīng)用所需的 gem 依賴。Bundler gem 需要用到這兩個(gè)文件。關(guān)于 Bundler 的更多介紹,請?jiān)L問 Bundler 官網(wǎng)。
lib/ 應(yīng)用的擴(kuò)展模塊。
log/ 應(yīng)用日志文件。
public/ 僅有的可以直接從外部訪問的文件夾,包含靜態(tài)文件和編譯后的靜態(tài)資源文件。
Rakefile 定位并加載可在命令行中執(zhí)行的任務(wù)。這些任務(wù)在 Rails 的各個(gè)組件中定義。如果要添加自定義任務(wù),請不要修改 Rakefile,真接把自定義任務(wù)保存在 lib/tasks 文件夾中即可。
README.md 應(yīng)用的自述文件,說明應(yīng)用的用途、安裝方法等。
test/ 單元測試、固件和其他測試裝置。詳情請參閱testing.xml。
tmp/ 臨時(shí)文件(如緩存和 PID 文件)。
vendor/ 包含第三方代碼,如第三方 gem。

4 Hello, Rails!

首先,讓我們快速地在頁面中添加一些文字。為了訪問頁面,需要運(yùn)行 Rails 應(yīng)用服務(wù)器(即 Web 服務(wù)器)。

4.1 啟動(dòng) Web 服務(wù)器

實(shí)際上這個(gè) Rails 應(yīng)用已經(jīng)可以正常運(yùn)行了。要訪問應(yīng)用,需要在開發(fā)設(shè)備中啟動(dòng) Web 服務(wù)器。請?jiān)?blog 文件夾中執(zhí)行下面的命令:

$ bin/rails server

Windows 用戶需要把 bin 文件夾下的腳本文件直接傳遞給 Ruby 解析器,例如 ruby bin\rails server。

編譯 CoffeeScript 和壓縮 JavaScript 靜態(tài)資源文件需要 JavaScript 運(yùn)行時(shí),如果沒有運(yùn)行時(shí),在壓縮靜態(tài)資源文件時(shí)會(huì)報(bào)錯(cuò),提示沒有 execjs。Mac OS X 和 Windows 一般都提供了 JavaScript 運(yùn)行時(shí)。在 Rails 應(yīng)用的 Gemfile 中,therubyracer gem 被注釋掉了,如果需要使用這個(gè) gem,請去掉注釋。對于 JRuby 用戶,推薦使用 therubyrhino 這個(gè)運(yùn)行時(shí),在 JRuby 中創(chuàng)建 Rails 應(yīng)用的 Gemfile 中默認(rèn)包含了這個(gè) gem。要查看 Rails 支持的所有運(yùn)行時(shí),請參閱 ExecJS

上述命令會(huì)啟動(dòng) Puma,這是 Rails 默認(rèn)使用的 Web 服務(wù)器。要查看運(yùn)行中的應(yīng)用,請打開瀏覽器窗口,訪問 http://localhost:3000。這時(shí)應(yīng)該看到默認(rèn)的 Rails 歡迎頁面:

默認(rèn)的 Rails 歡迎頁面

要停止 Web 服務(wù)器,請?jiān)诮K端中按 Ctrl+C 鍵。服務(wù)器停止后命令行提示符會(huì)重新出現(xiàn)。在大多數(shù)類 Unix 系統(tǒng)中,包括 Mac OS X,命令行提示符是 $ 符號。在開發(fā)模式中,一般情況下無需重啟服務(wù)器,服務(wù)器會(huì)自動(dòng)加載修改后的文件。

歡迎頁面是創(chuàng)建 Rails 應(yīng)用的冒煙測試,看到這個(gè)頁面就表示應(yīng)用已經(jīng)正確配置,能夠正常工作了。

4.2 顯示“Hello, Rails!”

要讓 Rails 顯示“Hello, Rails!”,需要?jiǎng)?chuàng)建控制器和視圖。

控制器接受向應(yīng)用發(fā)起的特定訪問請求。路由決定哪些訪問請求被哪些控制器接收。一般情況下,一個(gè)控制器會(huì)對應(yīng)多個(gè)路由,不同路由對應(yīng)不同動(dòng)作。動(dòng)作搜集數(shù)據(jù)并把數(shù)據(jù)提供給視圖。

視圖以人類能看懂的格式顯示數(shù)據(jù)。有一點(diǎn)要特別注意,數(shù)據(jù)是在控制器而不是視圖中獲取的,視圖只是顯示數(shù)據(jù)。默認(rèn)情況下,視圖模板使用 eRuby(嵌入式 Ruby)語言編寫,經(jīng)由 Rails 解析后,再發(fā)送給用戶。

可以用控制器生成器來創(chuàng)建控制器。下面的命令告訴控制器生成器創(chuàng)建一個(gè)包含“index”動(dòng)作的“Welcome”控制器:

$ bin/rails generate controller Welcome index

上述命令讓 Rails 生成了多個(gè)文件和一個(gè)路由:

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.coffee
invoke    scss
create      app/assets/stylesheets/welcome.scss

其中最重要的文件是控制器和視圖,控制器位于 app/controllers/welcome_controller.rb 文件 ,視圖位于 app/views/welcome/index.html.erb 文件 。

在文本編輯器中打開 app/views/welcome/index.html.erb 文件,刪除所有代碼,然后添加下面的代碼:

<h1>Hello, Rails!</h1>

4.3 設(shè)置應(yīng)用主頁

現(xiàn)在我們已經(jīng)創(chuàng)建了控制器和視圖,還需要告訴 Rails 何時(shí)顯示“Hello, Rails!”,我們希望在訪問根地址 http://localhost:3000 時(shí)顯示。目前根地址顯示的還是默認(rèn)的 Rails 歡迎頁面。

接下來需要告訴 Rails 真正的主頁在哪里。

在編輯器中打開 config/routes.rb 文件。

Rails.application.routes.draw do
  get 'welcome/index'
  # For details on the DSL available within this file, see http://guides./routing.html
end

這是應(yīng)用的路由文件,使用特殊的 DSL(domain-specific language,領(lǐng)域?qū)僬Z言)編寫,告訴 Rails 把訪問請求發(fā)往哪個(gè)控制器和動(dòng)作。編輯這個(gè)文件,添加一行代碼 root 'welcome#index',此時(shí)文件內(nèi)容應(yīng)該變成下面這樣:

Rails.application.routes.draw do
  get 'welcome/index'
  root 'welcome#index'
end

root 'welcome#index' 告訴 Rails 對根路徑的訪問請求應(yīng)該發(fā)往 welcome 控制器的 index 動(dòng)作,get 'welcome/index' 告訴 Rails 對 http://localhost:3000/welcome/index 的訪問請求應(yīng)該發(fā)往 welcome 控制器的 index 動(dòng)作。后者是之前使用控制器生成器創(chuàng)建控制器(bin/rails generate controller Welcome index)時(shí)自動(dòng)生成的。

如果在生成控制器時(shí)停止了服務(wù)器,請?jiān)俅螁?dòng)服務(wù)器(bin/rails server),然后在瀏覽器中訪問 http://localhost:3000。我們會(huì)看到之前添加到 app/views/welcome/index.html.erb 文件 的“Hello, Rails!”信息,這說明新定義的路由確實(shí)把訪問請求發(fā)往了 WelcomeControllerindex 動(dòng)作,并正確渲染了視圖。

關(guān)于路由的更多介紹,請參閱Rails 路由全解

5 啟動(dòng)并運(yùn)行起來

前文已經(jīng)介紹了如何創(chuàng)建控制器、動(dòng)作和視圖,接下來我們要?jiǎng)?chuàng)建一些更具實(shí)用價(jià)值的東西。

在 Blog 應(yīng)用中創(chuàng)建一個(gè)資源(resource)。資源是一個(gè)術(shù)語,表示一系列類似對象的集合,如文章、人或動(dòng)物。資源中的項(xiàng)目可以被創(chuàng)建、讀取、更新和刪除,這些操作簡稱 CRUD(Create, Read, Update, Delete)。

Rails 提供了 resources 方法,用于聲明標(biāo)準(zhǔn)的 REST 資源。把 article 資源添加到 config/routes.rb 文件,此時(shí)文件內(nèi)容應(yīng)該變成下面這樣:

Rails.application.routes.draw do
  resources :articles
  root 'welcome#index'
end

執(zhí)行 bin/rails routes 命令,可以看到所有標(biāo)準(zhǔn) REST 動(dòng)作都具有對應(yīng)的路由。輸出結(jié)果中各列的意義稍后會(huì)作說明,現(xiàn)在只需注意 Rails 從 article 的單數(shù)形式推導(dǎo)出了它的復(fù)數(shù)形式,并進(jìn)行了合理使用。

$ bin/rails 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

下一節(jié),我們將為應(yīng)用添加新建文章和查看文章的功能。這兩個(gè)操作分別對應(yīng)于 CRUD 的“C”和“R”:創(chuàng)建和讀取。下面是用于新建文章的表單:

用于新建文章的表單

表單看起來很簡陋,不過沒關(guān)系,之后我們再來美化。

5.1 打地基

首先,應(yīng)用需要一個(gè)頁面用于新建文章,/articles/new 是個(gè)不錯(cuò)的選擇。相關(guān)路由之前已經(jīng)定義過了,可以直接訪問。打開 http://localhost:3000/articles/new,會(huì)看到下面的路由錯(cuò)誤:

路由錯(cuò)誤,常量 ArticlesController 未初始化

產(chǎn)生錯(cuò)誤的原因是,用于處理該請求的控制器還沒有定義。解決問題的方法很簡單:創(chuàng)建 Articles 控制器。執(zhí)行下面的命令:

$ bin/rails generate controller Articles

打開剛剛生成的 app/controllers/articles_controller.rb 文件,會(huì)看到一個(gè)空的控制器:

class ArticlesController < ApplicationController
end

控制器實(shí)際上只是一個(gè)繼承自 ApplicationController 的類。接在來要在這個(gè)類中定義的方法也就是控制器的動(dòng)作。這些動(dòng)作對文章執(zhí)行 CRUD 操作。

在 Ruby 中,有 public、privateprotected 三種方法,其中只有 public 方法才能作為控制器的動(dòng)作。詳情請參閱 Programming Ruby 一書。

現(xiàn)在刷新 http://localhost:3000/articles/new,會(huì)看到一個(gè)新錯(cuò)誤:

未知?jiǎng)幼鳎?ArticlesController 中找不到 new 動(dòng)作

這個(gè)錯(cuò)誤的意思是,Rails 在剛剛生成的 ArticlesController 中找不到 new 動(dòng)作。這是因?yàn)樵?Rails 中生成控制器時(shí),如果不指定想要的動(dòng)作,生成的控制器就會(huì)是空的。

在控制器中手動(dòng)定義動(dòng)作,只需要定義一個(gè)新方法。打開 app/controllers/articles_controller.rb 文件,在 ArticlesController 類中定義 new 方法,此時(shí)控制器應(yīng)該變成下面這樣:

class ArticlesController < ApplicationController
  def new
  end
end

ArticlesController 中定義 new 方法后,再次刷新 http://localhost:3000/articles/new,會(huì)看到另一個(gè)錯(cuò)誤:

未知格式,缺少對應(yīng)模板

產(chǎn)生錯(cuò)誤的原因是,Rails 要求這樣的常規(guī)動(dòng)作有用于顯示數(shù)據(jù)的對應(yīng)視圖。如果沒有視圖可用,Rails 就會(huì)拋出異常。

上圖中下面的幾行都被截?cái)嗔?,下面是完整信息?/p>

ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you’re loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we’re showing an error to be extra-clear. If you expect 204 No Content, carry on. That’s what you’ll get from an XHR or API request. Give it a shot.

內(nèi)容還真不少!讓我們快速瀏覽一下,看看各部分是什么意思。

第一部分說明缺少哪個(gè)模板,這里缺少的是 articles/new 模板。Rails 首先查找這個(gè)模板,如果找不到再查找 application/new 模板。之所以會(huì)查找后面這個(gè)模板,是因?yàn)?ArticlesController 繼承自 ApplicationController。

下一部分是 request.formats,說明響應(yīng)使用的模板格式。當(dāng)我們在瀏覽器中請求頁面時(shí),request.formats 的值是 text/html,因此 Rails 會(huì)查找 HTML 模板。request.variants 指明伺服的是何種物理設(shè)備,幫助 Rails 判斷該使用哪個(gè)模板渲染響應(yīng)。它的值是空的,因?yàn)闆]有為其提供信息。

在本例中,能夠工作的最簡單的模板位于 app/views/articles/new.html.erb 文件中。文件的擴(kuò)展名很重要:第一個(gè)擴(kuò)展名是模板格式,第二個(gè)擴(kuò)展名是模板處理器。Rails 會(huì)嘗試在 app/views 文件夾中查找 articles/new 模板。這個(gè)模板的格式只能是 html,模板處理器只能是 erb、buildercoffee 中的一個(gè)。:erb 是最常用的 HTML 模板處理器,:builder 是 XML 模板處理器,:coffee 模板處理器用 CoffeeScript 創(chuàng)建 JavaScript 模板。因?yàn)槲覀円獎(jiǎng)?chuàng)建 HTML 表單,所以應(yīng)該使用能夠在 HTML 中嵌入 Ruby 的 ERB 語言。

所以我們需要?jiǎng)?chuàng)建 articles/new.html.erb 文件,并把它放在應(yīng)用的 app/views 文件夾中。

現(xiàn)在讓我們繼續(xù)前進(jìn)。新建 app/views/articles/new.html.erb 文件,添加下面的代碼:

<h1>New Article</h1>

刷新 http://localhost:3000/articles/new,會(huì)看到頁面有了標(biāo)題。現(xiàn)在路由、控制器、動(dòng)作和視圖都可以協(xié)調(diào)地工作了!是時(shí)候創(chuàng)建用于新建文章的表單了。

5.2 第一個(gè)表單

在模板中創(chuàng)建表單,可以使用表單構(gòu)建器。Rails 中最常用的表單構(gòu)建器是 form_for 輔助方法。讓我們使用這個(gè)方法,在 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 %>

現(xiàn)在刷新頁面,會(huì)看到和前文截圖一樣的表單。在 Rails 中創(chuàng)建表單就是這么簡單!

調(diào)用 form_for 輔助方法時(shí),需要為表單傳遞一個(gè)標(biāo)識對象作為參數(shù),這里是 :article 符號。這個(gè)符號告訴 form_for 輔助方法表單用于處理哪個(gè)對象。在 form_for 輔助方法的塊中,f 表示 FormBuilder 對象,用于創(chuàng)建兩個(gè)標(biāo)簽和兩個(gè)文本字段,分別用于添加文章的標(biāo)題和正文。最后在 f 對象上調(diào)用 submit 方法來為表單創(chuàng)建提交按鈕。

不過這個(gè)表單還有一個(gè)問題,查看 HTML 源代碼會(huì)看到表單 action 屬性的值是 /articles/new,指向的是當(dāng)前頁面,而當(dāng)前頁面只是用于顯示新建文章的表單。

應(yīng)該把表單指向其他 URL,為此可以使用 form_for 輔助方法的 :url 選項(xiàng)。在 Rails 中習(xí)慣用 create 動(dòng)作來處理提交的表單,因此應(yīng)該把表單指向這個(gè)動(dòng)作。

修改 app/views/articles/new.html.erb 文件的 form_for 這一行,改為:

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

這里我們把 articles_path 輔助方法傳遞給 :url 選項(xiàng)。要想知道這個(gè)方法有什么用,我們可以回過頭看一下 bin/rails routes 的輸出結(jié)果:

$ bin/rails 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 前綴相關(guān)聯(lián)的 URI 模式。默認(rèn)情況下,表單會(huì)向這個(gè)路由發(fā)起 POST 請求。這個(gè)路由和當(dāng)前控制器 ArticlesControllercreate 動(dòng)作相關(guān)聯(lián)。

有了表單和與之相關(guān)聯(lián)的路由,我們現(xiàn)在可以填寫表單,然后點(diǎn)擊提交按鈕來新建文章了,請實(shí)際操作一下。提交表單后,會(huì)看到一個(gè)熟悉的錯(cuò)誤:

未知?jiǎng)幼鳎?`ArticlesController` 中找不到 `create` 動(dòng)作

解決問題的方法是在 ArticlesController 中創(chuàng)建 create 動(dòng)作。

5.3 創(chuàng)建文章

要消除“未知?jiǎng)幼鳌卞e(cuò)誤,我們需要修改 app/controllers/articles_controller.rb 文件,在 ArticlesController 類的 new 動(dòng)作之后添加 create 動(dòng)作,就像下面這樣:

class ArticlesController < ApplicationController
  def new
  end
  def create
  end
end

現(xiàn)在重新提交表單,會(huì)看到什么都沒有改變。別著急!這是因?yàn)楫?dāng)我們沒有說明動(dòng)作的響應(yīng)是什么時(shí),Rails 默認(rèn)返回 204 No Content response。我們剛剛添加了 create 動(dòng)作,但沒有說明響應(yīng)是什么。這里,create 動(dòng)作應(yīng)該把新建文章保存到數(shù)據(jù)庫中。

表單提交后,其字段以參數(shù)形式傳遞給 Rails,然后就可以在控制器動(dòng)作中引用這些參數(shù),以執(zhí)行特定任務(wù)。要想查看這些參數(shù)的內(nèi)容,可以把 create 動(dòng)作的代碼修改成下面這樣:

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

這里 render 方法接受了一個(gè)簡單的散列(hash)作為參數(shù),:plain 鍵的值是 params[:article].inspect。params 方法是代表表單提交的參數(shù)(或字段)的對象。params 方法返回 ActionController::Parameters 對象,這個(gè)對象允許使用字符串或符號訪問散列的鍵。這里我們只關(guān)注通過表單提交的參數(shù)。

請確保牢固掌握 params 方法,這個(gè)方法很常用。讓我們看一個(gè)示例 URL:http://www./?username=dhh&email=dhh@email.com。在這個(gè) URL 中,params[:username] 的值是“dhh”,params[:email] 的值是“dhh@email.com”。

如果再次提交表單,就不會(huì)再看到缺少模板錯(cuò)誤,而是會(huì)看到下面這些內(nèi)容:

<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>

create 動(dòng)作把表單提交的參數(shù)都顯示出來了,但這并沒有什么用,只是看到了參數(shù)實(shí)際上卻什么也沒做。

5.4 創(chuàng)建 Article 模型

在 Rails 中,模型使用單數(shù)名稱,對應(yīng)的數(shù)據(jù)庫表使用復(fù)數(shù)名稱。Rails 提供了用于創(chuàng)建模型的生成器,大多數(shù) Rails 開發(fā)者在新建模型時(shí)傾向于使用這個(gè)生成器。要想新建模型,請執(zhí)行下面的命令:

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

上面的命令告訴 Rails 創(chuàng)建 Article 模型,并使模型具有字符串類型的 title 屬性和文本類型的 text 屬性。這兩個(gè)屬性會(huì)自動(dòng)添加到數(shù)據(jù)庫的 articles 表中,并映射到 Article 模型上。

為此 Rails 會(huì)創(chuàng)建一堆文件。這里我們只關(guān)注 app/models/article.rbdb/migrate/20140120191729_create_articles.rb 這兩個(gè)文件 (后面這個(gè)文件名和你看到的可能會(huì)有點(diǎn)不一樣)。后者負(fù)責(zé)創(chuàng)建數(shù)據(jù)庫結(jié)構(gòu),下一節(jié)會(huì)詳細(xì)說明。

Active Record 很智能,能自動(dòng)把數(shù)據(jù)表的字段名映射到模型屬性上,因此無需在 Rails 模型中聲明屬性,讓 Active Record 自動(dòng)完成即可。

5.5 運(yùn)行遷移

如前文所述,bin/rails generate model 命令會(huì)在 db/migrate 文件夾中生成數(shù)據(jù)庫遷移文件。遷移是用于簡化創(chuàng)建和修改數(shù)據(jù)庫表操作的 Ruby 類。Rails 使用 rake 命令運(yùn)行遷移,并且在遷移作用于數(shù)據(jù)庫之后還可以撤銷遷移操作。遷移的文件名包含了時(shí)間戳,以確保遷移按照創(chuàng)建時(shí)間順序運(yùn)行。

讓我們看一下 db/migrate/YYYYMMDDHHMMSS_create_articles.rb 文件(記住,你的文件名可能會(huì)有點(diǎn)不一樣),會(huì)看到下面的內(nèi)容:

class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text
      t.timestamps
    end
  end
end

上面的遷移創(chuàng)建了 change 方法,在運(yùn)行遷移時(shí)會(huì)調(diào)用這個(gè)方法。在 change 方法中定義的操作都是可逆的,在需要時(shí) Rails 知道如何撤銷這些操作。運(yùn)行遷移后會(huì)創(chuàng)建 articles 表,這個(gè)表包括一個(gè)字符串字段和一個(gè)文本字段,以及兩個(gè)用于跟蹤文章創(chuàng)建和更新時(shí)間的時(shí)間戳字段。

關(guān)于遷移的更多介紹,請參閱Active Record 遷移。

現(xiàn)在可以使用 bin/rails 命令運(yùn)行遷移了:

$ bin/rails db:migrate

Rails 會(huì)執(zhí)行遷移命令并告訴我們它創(chuàng)建了 Articles 表。

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

因?yàn)槟J(rèn)情況下我們是在開發(fā)環(huán)境中工作,所以上述命令應(yīng)用于 config/database.yml 文件中 development 部分定義的的數(shù)據(jù)庫。要想在其他環(huán)境中執(zhí)行遷移,例如生產(chǎn)環(huán)境,就必須在調(diào)用命令時(shí)顯式傳遞環(huán)境變量:bin/rails db:migrate RAILS_ENV=production。

5.6 在控制器中保存數(shù)據(jù)

回到 ArticlesController,修改 create 動(dòng)作,使用新建的 Article 模型把數(shù)據(jù)保存到數(shù)據(jù)庫。打開 app/controllers/articles_controller.rb 文件,像下面這樣修改 create 動(dòng)作:

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

讓我們看一下上面的代碼都做了什么:Rails 模型可以用相應(yīng)的屬性初始化,它們會(huì)自動(dòng)映射到對應(yīng)的數(shù)據(jù)庫字段。create 動(dòng)作中的第一行代碼完成的就是這個(gè)操作(記住,params[:article] 包含了我們想要的屬性)。接下來 @article.save 負(fù)責(zé)把模型保存到數(shù)據(jù)庫。最后把頁面重定向到 show 動(dòng)作,這個(gè) show 動(dòng)作我們稍后再定義。

你可能想知道,為什么在上面的代碼中 Article.newA 是大寫的,而在本文的其他地方引用 articles 時(shí)大都是小寫的。因?yàn)檫@里我們引用的是在 app/models/article.rb 文件中定義的 Article 類,而在 Ruby 中類名必須以大寫字母開頭。

之后我們會(huì)看到,@article.save 返回布爾值,以表明文章是否保存成功。

現(xiàn)在訪問 http://localhost:3000/articles/new,我們就快要能夠創(chuàng)建文章了,但我們還會(huì)看到下面的錯(cuò)誤:

禁用屬性錯(cuò)誤

Rails 提供了多種安全特性來幫助我們編寫安全的應(yīng)用,上面看到的就是一種安全特性。這個(gè)安全特性叫做 健壯參數(shù)(strong parameter),要求我們明確地告訴 Rails 哪些參數(shù)允許在控制器動(dòng)作中使用。

為什么我們要這樣自找麻煩呢?一次性獲取所有控制器參數(shù)并自動(dòng)賦值給模型顯然更簡單,但這樣做會(huì)造成惡意使用的風(fēng)險(xiǎn)。設(shè)想一下,如果有人對服務(wù)器發(fā)起了一個(gè)精心設(shè)計(jì)的請求,看起來就像提交了一篇新文章,但同時(shí)包含了能夠破壞應(yīng)用完整性的額外字段和值,會(huì)怎么樣?這些惡意數(shù)據(jù)會(huì)批量賦值給模型,然后和正常數(shù)據(jù)一起進(jìn)入數(shù)據(jù)庫,這樣就有可能破壞我們的應(yīng)用或者造成更大損失。

所以我們只能為控制器參數(shù)設(shè)置白名單,以避免錯(cuò)誤地批量賦值。這里,我們想在 create 動(dòng)作中合法使用 titletext 參數(shù),為此需要使用 requirepermit 方法。像下面這樣修改 create 動(dòng)作中的一行代碼:

@article = Article.new(params.require(:article).permit(:title, :text))

上述代碼通常被抽象為控制器類的一個(gè)方法,以便在控制器的多個(gè)動(dòng)作中重用,例如在 createupdate 動(dòng)作中都會(huì)用到。除了批量賦值問題,為了禁止從外部調(diào)用這個(gè)方法,通常還要把它設(shè)置為 private。最后的代碼像下面這樣:

def create
  @article = Article.new(article_params)
  @article.save
  redirect_to @article
end
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

關(guān)于鍵壯參數(shù)的更多介紹,請參閱上面提供的參考資料和這篇博客。

5.7 顯示文章

現(xiàn)在再次提交表單,Rails 會(huì)提示找不到 show 動(dòng)作。盡管這個(gè)提示沒有多大用處,但在繼續(xù)前進(jìn)之前我們還是先添加 show 動(dòng)作吧。

之前我們在 bin/rails routes 命令的輸出結(jié)果中看到,show 動(dòng)作對應(yīng)的路由是:

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

特殊句法 :id 告訴 Rails 這個(gè)路由期望接受 :id 參數(shù),在這里也就是文章的 ID。

和前面一樣,我們需要在 app/controllers/articles_controller.rb 文件中添加 show 動(dòng)作,并創(chuàng)建對應(yīng)的視圖文件。

常見的做法是按照以下順序在控制器中放置標(biāo)準(zhǔn)的 CRUD 動(dòng)作:index,show,newedit,createupdatedestroy。你也可以按照自己的順序放置這些動(dòng)作,但要記住它們都是公開方法,如前文所述,必須放在控制器的私有方法或受保護(hù)的方法之前才能正常工作。

有鑒于此,讓我們像下面這樣添加 show 動(dòng)作:

class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
  end
  def new
  end
  # 為了行文簡潔,省略以下內(nèi)容

上面的代碼中有幾個(gè)問題需要注意。我們使用 Article.find 來查找文章,并傳入 params[:id] 以便從請求中獲得 :id 參數(shù)。我們還使用實(shí)例變量(前綴為 @)保存對文章對象的引用。這樣做是因?yàn)?Rails 會(huì)把所有實(shí)例變量傳遞給視圖。

現(xiàn)在新建 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 列出所有文章

我們還需要列出所有文章,下面就來完成這個(gè)功能。在 bin/rails routes 命令的輸出結(jié)果中,和列出文章對應(yīng)的路由是:

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

app/controllers/articles_controller.rb 文件的 ArticlesController 中為上述路由添加對應(yīng)的 index 動(dòng)作。在編寫 index 動(dòng)作時(shí),常見的做法是把它作為控制器的第一個(gè)方法,就像下面這樣:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
  def show
    @article = Article.find(params[:id])
  end
  def new
  end
  # 為了行文簡潔,省略以下內(nèi)容

最后,在 app/views/articles/index.html.erb 文件中為 index 動(dòng)作添加視圖:

<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>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>

現(xiàn)在訪問 http://localhost:3000/articles,會(huì)看到已創(chuàng)建的所有文章的列表。

5.9 添加鏈接

至此,我們可以創(chuàng)建、顯示、列出文章了。下面我們添加一些指向這些頁面的鏈接。

打開 app/views/welcome/index.html.erb 文件,修改成下面這樣:

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

link_to 方法是 Rails 內(nèi)置的視圖輔助方法之一,用于創(chuàng)建基于鏈接文本和地址的超鏈接。在這里地址指的是文章列表頁面的路徑。

接下來添加指向其他視圖的鏈接。首先在 app/views/articles/index.html.erb 文件中添加“New Article”鏈接,把這個(gè)鏈接放在 <table> 標(biāo)簽之前:

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

點(diǎn)擊這個(gè)鏈接會(huì)打開用于新建文章的表單。

接下來在 app/views/articles/new.html.erb 文件中添加返回 index 動(dòng)作的鏈接,把這個(gè)鏈接放在表單之后:

<%= form_for :article, url: articles_path do |f| %>
  ...
<% end %>
<%= link_to 'Back', articles_path %>

最后,在 app/views/articles/show.html.erb 模板中添加返回 index 動(dòng)作的鏈接,這樣用戶看完一篇文章后就可以返回文章列表頁面了:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>

鏈接到當(dāng)前控制器的動(dòng)作時(shí)不需要指定 :controller 選項(xiàng),因?yàn)?Rails 默認(rèn)使用當(dāng)前控制器。

在開發(fā)環(huán)境中(默認(rèn)情況下我們是在開發(fā)環(huán)境中工作),Rails 針對每個(gè)瀏覽器請求都會(huì)重新加載應(yīng)用,因此對應(yīng)用進(jìn)行修改之后不需要重啟服務(wù)器。

5.10 添加驗(yàn)證

app/models/article.rb 模型文件簡單到只有兩行代碼:

class Article < ApplicationRecord
end

雖然這個(gè)文件中代碼很少,但請注意 Article 類繼承自 ApplicationRecord 類,而 ApplicationRecord 類繼承自 ActiveRecord::Base 類。正是 ActiveRecord::Base 類為 Rails 模型提供了大量功能,包括基本的數(shù)據(jù)庫 CRUD 操作(創(chuàng)建、讀取、更新、刪除)、數(shù)據(jù)驗(yàn)證,以及對復(fù)雜搜索的支持和關(guān)聯(lián)多個(gè)模型的能力。

Rails 提供了許多方法用于驗(yàn)證傳入模型的數(shù)據(jù)。打開 app/models/article.rb 文件,像下面這樣修改:

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

添加的代碼用于確保每篇文章都有標(biāo)題,并且標(biāo)題長度不少于 5 個(gè)字符。在 Rails 模型中可以驗(yàn)證多種條件,包括字段是否存在、字段是否唯一、字段的格式、關(guān)聯(lián)對象是否存在,等等。關(guān)于驗(yàn)證的更多介紹,請參閱active_record_validations.xml

現(xiàn)在驗(yàn)證已經(jīng)添加完畢,如果我們在調(diào)用 @article.save 時(shí)傳遞了無效的文章數(shù)據(jù),驗(yàn)證就會(huì)返回 false。再次打開 app/controllers/articles_controller.rb 文件,會(huì)看到我們并沒有在 create 動(dòng)作中檢查 @article.save 的調(diào)用結(jié)果。在這里如果 @article.save 失敗了,就需要把表單再次顯示給用戶。為此,需要像下面這樣修改 app/controllers/articles_controller.rb 文件中的 newcreate 動(dòng)作:

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 動(dòng)作中創(chuàng)建了新的實(shí)例變量 @article,稍后你就會(huì)知道為什么要這樣做。

注意在 create 動(dòng)作中,當(dāng) save 返回 false 時(shí),我們用 render 代替了 redirect_to。使用 render 方法是為了把 @article 對象回傳給 new 模板。這里渲染操作是在提交表單的這個(gè)請求中完成的,而 redirect_to 會(huì)告訴瀏覽器發(fā)起另一個(gè)請求。

刷新 http://localhost:3000/articles/new,試著提交一篇沒有標(biāo)題的文章,Rails 會(huì)返回這個(gè)表單,但這種處理方式?jīng)]有多大用處,更好的做法是告訴用戶哪里出錯(cuò)了。為此需要修改 app/views/articles/new.html.erb 文件,添加顯示錯(cuò)誤信息的代碼:

<%= 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? 檢查是否有錯(cuò)誤,如果有錯(cuò)誤就使用 @article.errors.full_messages 列出所有錯(cuò)誤信息。

pluralize 是 Rails 提供的輔助方法,接受一個(gè)數(shù)字和一個(gè)字符串作為參數(shù)。如果數(shù)字比 1 大,字符串會(huì)被自動(dòng)轉(zhuǎn)換為復(fù)數(shù)形式。

ArticlesController 中添加 @article = Article.new 是因?yàn)槿绻贿@樣做,在視圖中 @article 的值就會(huì)是 nil,這樣在調(diào)用 @article.errors.any? 時(shí)就會(huì)拋出錯(cuò)誤。

Rails 會(huì)自動(dòng)用 div 包圍含有錯(cuò)誤信息的字段,并為這些 div 添加 field_with_errors 類。我們可以定義 CSS 規(guī)則突出顯示錯(cuò)誤信息。

當(dāng)我們再次訪問 http://localhost:3000/articles/new,試著提交一篇沒有標(biāo)題的文章,就會(huì)看到友好的錯(cuò)誤信息。

出錯(cuò)的表單

5.11 更新文章

我們已經(jīng)介紹了 CRUD 操作中的“CR”兩種操作,下面讓我們看一下“U”操作,也就是更新文章。

第一步要在 ArticlesController 中添加 edit 動(dòng)作,通常把這個(gè)動(dòng)作放在 new 動(dòng)作和 create 動(dòng)作之間,就像下面這樣:

def new
  @article = Article.new
end
def edit
  @article = Article.find(params[:id])
end
def create
  @article = Article.new(article_params)
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

接下來在視圖中添加一個(gè)表單,這個(gè)表單類似于前文用于新建文章的表單。創(chuàng)建 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 動(dòng)作,這個(gè)動(dòng)作稍后我們再來定義。

method: :patch 選項(xiàng)告訴 Rails 使用 PATCH 方法提交表單。根據(jù) REST 協(xié)議,PATCH 方法是更新資源時(shí)使用的 HTTP 方法。

form_for 輔助方法的第一個(gè)參數(shù)可以是對象,例如 @articleform_for 輔助方法會(huì)用這個(gè)對象的字段來填充表單。如果傳入和實(shí)例變量(@article)同名的符號(:article),也會(huì)自動(dòng)產(chǎn)生相同效果,上面的代碼使用的就是符號。關(guān)于 form_for 輔助方法參數(shù)的更多介紹,請參閱 form_for 的文檔。

接下來在 app/controllers/articles_controller.rb 文件中創(chuàng)建 update 動(dòng)作,把這個(gè)動(dòng)作放在 create 動(dòng)作和 private 方法之間:

def create
  @article = Article.new(article_params)
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end
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 動(dòng)作用于更新已有記錄,它接受一個(gè)散列作為參數(shù),散列中包含想要更新的屬性。和之前一樣,如果更新文章時(shí)發(fā)生錯(cuò)誤,就需要把表單再次顯示給用戶。

上面的代碼重用了之前為 create 動(dòng)作定義的 article_params 方法。

不用把所有屬性都傳遞給 update 方法。例如,調(diào)用 @article.update(title: 'A new title') 時(shí),Rails 只更新 title 屬性而不修改其他屬性。

最后,我們想在文章列表中顯示指向 edit 動(dòng)作的鏈接。打開 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 鏈接,這樣文章頁面也有 Edit 鏈接了。把這個(gè)鏈接添加到模板底部:

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

下面是文章列表現(xiàn)在的樣子:

文章列表

5.12 使用局部視圖去掉視圖中的重復(fù)代碼

編輯文章頁面和新建文章頁面看起來很相似,實(shí)際上這兩個(gè)頁面用于顯示表單的代碼是相同的。現(xiàn)在我們要用局部視圖來去掉這些重復(fù)代碼。按照約定,局部視圖的文件名以下劃線開頭。

關(guān)于局部視圖的更多介紹,請參閱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 的用法變了之外,其他代碼都和之前一樣。之所以能用這個(gè)更短、更簡單的 form_for 聲明來代替新建文章頁面和編輯文章頁面的兩個(gè)表單,是因?yàn)?@article 是一個(gè)資源,對應(yīng)于一套 REST 式路由,Rails 能夠推斷出應(yīng)該使用哪個(gè)地址和方法。關(guān)于 form_for 用法的更多介紹,請參閱“面向資源的風(fēng)格”。

現(xiàn)在更新 app/views/articles/new.html.erb 視圖,以使用新建的局部視圖。把文件內(nèi)容替換為下面的代碼:

<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 刪除文章

現(xiàn)在該介紹 CRUD 中的“D”操作了,也就是從數(shù)據(jù)庫刪除文章。按照 REST 架構(gòu)的約定,在 bin/rails routes 命令的輸出結(jié)果中刪除文章的路由是:

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

刪除資源的路由應(yīng)該使用 delete 路由方法。如果在刪除資源時(shí)仍然使用 get 路由,就可能給那些設(shè)計(jì)惡意地址的人提供可乘之機(jī):

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

我們用 delete 方法來刪除資源,對應(yīng)的路由會(huì)映射到 app/controllers/articles_controller.rb 文件中的 destroy 動(dòng)作,稍后我們要?jiǎng)?chuàng)建這個(gè)動(dòng)作。destroy 動(dòng)作是控制器中的最后一個(gè) CRUD 動(dòng)作,和其他公共 CRUD 動(dòng)作一樣,這個(gè)動(dòng)作應(yīng)該放在 privateprotected 方法之前。打開 app/controllers/articles_controller.rb 文件,添加下面的代碼:

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

app/controllers/articles_controller.rb 文件中,ArticlesController 的完整代碼應(yīng)該像下面這樣:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
  def show
    @article = Article.find(params[:id])
  end
  def new
    @article = Article.new
  end
  def edit
    @article = Article.find(params[:id])
  end
  def create
    @article = Article.new(article_params)
    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end
  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end
  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    redirect_to articles_path
  end
  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

在 Active Record 對象上調(diào)用 destroy 方法,就可從數(shù)據(jù)庫中刪除它們。注意,我們不需要為 destroy 動(dòng)作添加視圖,因?yàn)橥瓿刹僮骱笏鼤?huì)重定向到 index 動(dòng)作。

最后,在 index 動(dòng)作的模板(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>

在上面的代碼中,link_to 輔助方法生成“Destroy”鏈接的用法有點(diǎn)不同,其中第二個(gè)參數(shù)是具名路由(named route),還有一些選項(xiàng)作為其他參數(shù)。method: :deletedata: { confirm: 'Are you sure?' } 選項(xiàng)用于設(shè)置鏈接的 HTML5 屬性,這樣點(diǎn)擊鏈接后 Rails 會(huì)先向用戶顯示一個(gè)確認(rèn)對話框,然后用 delete 方法發(fā)起請求。這些操作是通過 JavaScript 腳本 jquery_ujs 實(shí)現(xiàn)的,這個(gè)腳本在生成應(yīng)用骨架時(shí)已經(jīng)被自動(dòng)包含在了應(yīng)用的布局中(app/views/layouts/application.html.erb)。如果沒有這個(gè)腳本,確認(rèn)對話框就無法顯示。

確認(rèn)對話框

關(guān)于 jQuery 非侵入式適配器(jQuery UJS)的更多介紹,請參閱在 Rails 中使用 JavaScript。

恭喜你!現(xiàn)在你已經(jīng)可以創(chuàng)建、顯示、列出、更新和刪除文章了!

通常 Rails 鼓勵(lì)用資源對象來代替手動(dòng)聲明路由。關(guān)于路由的更多介紹,請參閱Rails 路由全解。

6 添加第二個(gè)模型

現(xiàn)在是為應(yīng)用添加第二個(gè)模型的時(shí)候了。這個(gè)模型用于處理文章評論。

6.1 生成模型

接下來將要使用的生成器,和之前用于創(chuàng)建 Article 模型的一樣。這次我們要?jiǎng)?chuàng)建 Comment 模型,用于保存文章評論。在終端中執(zhí)行下面的命令:

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

上面的命令會(huì)生成 4 個(gè)文件:

文件 用途
db/migrate/20140120201010_create_comments.rb 用于在數(shù)據(jù)庫中創(chuàng)建 comments 表的遷移文件(你的文件名會(huì)包含不同的時(shí)間戳)
app/models/comment.rb Comment 模型文件
test/models/comment_test.rb Comment 模型的測試文件
test/fixtures/comments.yml 用于測試的示例評論

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

class Comment < ApplicationRecord
  belongs_to :article
end

可以看到,Comment 模型文件的內(nèi)容和之前的 Article 模型差不多,僅僅多了一行 belongs_to :article,這行代碼用于建立 Active Record 關(guān)聯(lián)。下一節(jié)會(huì)簡單介紹關(guān)聯(lián)。

在上面的 Bash 命令中使用的 :references 關(guān)鍵字是一種特殊的模型數(shù)據(jù)類型,用于在數(shù)據(jù)表中新建字段。這個(gè)字段以提供的模型名加上 _id 后綴作為字段名,保存整數(shù)值。之后通過分析 db/schema.rb 文件可以更好地理解這些內(nèi)容。

除了模型文件,Rails 還生成了遷移文件,用于創(chuàng)建對應(yīng)的數(shù)據(jù)表:

class CreateComments < ActiveRecord::Migration[5.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, foreign_key: true
      t.timestamps
    end
  end
end

t.references 這行代碼創(chuàng)建 article_id 整數(shù)字段,為這個(gè)字段建立索引,并建立指向 articles 表的 id 字段的外鍵約束。下面運(yùn)行這個(gè)遷移:

$ bin/rails db:migrate

Rails 很智能,只會(huì)運(yùn)行針對當(dāng)前數(shù)據(jù)庫還沒有運(yùn)行過的遷移,運(yùn)行結(jié)果像下面這樣:

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

6.2 模型關(guān)聯(lián)

Active Record 關(guān)聯(lián)讓我們可以輕易地聲明兩個(gè)模型之間的關(guān)系。對于評論和文章,我們可以像下面這樣聲明:

  • 每一條評論都屬于某一篇文章

  • 一篇文章可以有多條評論

實(shí)際上,這種表達(dá)方式和 Rails 用于聲明模型關(guān)聯(lián)的句法非常接近。前文我們已經(jīng)看過 Comment 模型中用于聲明模型關(guān)聯(lián)的代碼,這行代碼用于聲明每一條評論都屬于某一篇文章:

class Comment < ApplicationRecord
  belongs_to :article
end

現(xiàn)在修改 app/models/article.rb 文件來添加模型關(guān)聯(lián)的另一端:

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

這兩行聲明能夠啟用一些自動(dòng)行為。例如,如果 @article 實(shí)例變量表示一篇文章,就可以使用 @article.comments 以數(shù)組形式取回這篇文章的所有評論。

關(guān)于模型關(guān)聯(lián)的更多介紹,請參閱Active Record 關(guān)聯(lián)

6.3 為評論添加路由

welcome 控制器一樣,在添加路由之后 Rails 才知道在哪個(gè)地址上查看評論。再次打開 config/routes.rb 文件,像下面這樣進(jìn)行修改:

resources :articles do
  resources :comments
end

上面的代碼在 articles 資源中創(chuàng)建 comments 資源,這種方式被稱為嵌套資源。這是表明文章和評論之間層級關(guān)系的另一種方式。

關(guān)于路由的更多介紹,請參閱Rails 路由全解。

6.4 生成控制器

有了模型,下面應(yīng)該創(chuàng)建對應(yīng)的控制器了。還是使用前面用過的生成器:

$ bin/rails generate controller Comments

上面的命令會(huì)創(chuàng)建 5 個(gè)文件和一個(gè)空文件夾:

文件/文件夾 用途
app/controllers/comments_controller.rb Comments 控制器文件
app/views/comments/ 控制器的視圖保存在這里
test/controllers/comments_controller_test.rb 控制器的測試文件
app/helpers/comments_helper.rb 視圖輔助方法文件
app/assets/javascripts/comment.coffee 控制器的 CoffeeScript 文件
app/assets/stylesheets/comment.scss 控制器的樣式表文件

在博客中,讀者看完文章后可以直接發(fā)表評論,并且馬上可以看到這些評論是否在頁面上顯示出來了。我們的博客采取同樣的設(shè)計(jì)。這里 CommentsController 需要提供創(chuàng)建評論和刪除垃圾評論的方法。

首先修改顯示文章的模板(app/views/articles/show.html.erb),添加發(fā)表評論的功能:

<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 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

上面的代碼在顯示文章的頁面中添加了用于新建評論的表單,通過調(diào)用 CommentsControllercreate 動(dòng)作來發(fā)表評論。這里 form_for 輔助方法以數(shù)組為參數(shù),會(huì)創(chuàng)建嵌套路由,例如 /articles/1/comments。

接下來在 app/controllers/comments_controller.rb 文件中添加 create 動(dòng)作:

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

上面的代碼比 Articles 控制器的代碼復(fù)雜得多,這是嵌套帶來的副作用。對于每一個(gè)發(fā)表評論的請求,都必須記錄這條評論屬于哪篇文章,因此需要在 Article 模型上調(diào)用 find 方法來獲取文章對象。

此外,上面的代碼還利用了關(guān)聯(lián)特有的方法,在 @article.comments 上調(diào)用 create 方法來創(chuàng)建和保存評論,同時(shí)自動(dòng)把評論和對應(yīng)的文章關(guān)聯(lián)起來。

添加評論后,我們使用 article_path(@article) 輔助方法把用戶帶回原來的文章頁面。如前文所述,這里調(diào)用了 ArticlesControllershow 動(dòng)作來渲染 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', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

現(xiàn)在可以在我們的博客中為文章添加評論了,評論添加后就會(huì)顯示在正確的位置上。

帶有評論的文章

7 重構(gòu)

現(xiàn)在博客的文章和評論都已經(jīng)正常工作,打開 app/views/articles/show.html.erb 文件,會(huì)看到文件代碼變得又長又不美觀。因此下面我們要用局部視圖來重構(gòu)代碼。

7.1 渲染局部視圖集合

首先創(chuàng)建評論的局部視圖,把顯示文章評論的代碼抽出來。創(chuàng)建 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', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

這樣對于 @article.comments 集合中的每條評論,都會(huì)渲染 app/views/comments/_comment.html.erb 文件中的局部視圖。render 方法會(huì)遍歷 @article.comments 集合,把每條評論賦值給局部視圖中的同名局部變量,也就是這里的 comment 變量。

7.2 渲染局部視圖表單

我們把添加評論的代碼也移到局部視圖中。創(chuàng)建 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', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

上面的代碼中第二個(gè) render 方法的參數(shù)就是我們剛剛定義的 comments/form 局部視圖。Rails 很智能,能夠發(fā)現(xiàn)字符串中的斜線,并意識到我們想渲染 app/views/comments 文件夾中的 _form.html.erb 文件。

@article 是實(shí)例變量,因此在所有局部視圖中都可以使用。

8 刪除評論

博客還有一個(gè)重要功能是刪除垃圾評論。為了實(shí)現(xiàn)這個(gè)功能,我們需要在視圖中添加一個(gè)鏈接,并在 CommentsController 中添加 destroy 動(dòng)作。

首先在 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>

點(diǎn)擊“Destroy Comment”鏈接后,會(huì)向 CommentsController 發(fā)起 DELETE /articles/:article_id/comments/:id 請求,這個(gè)請求將用于刪除指定評論。下面在控制器(app/controllers/comments_controller.rb)中添加 destroy 動(dòng)作:

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 動(dòng)作首先找到指定文章,然后在 @article.comments 集合中找到指定評論,接著從數(shù)據(jù)庫刪除這條評論,最后重定向到顯示文章的頁面。

8.1 刪除關(guān)聯(lián)對象

如果要?jiǎng)h除一篇文章,文章的相關(guān)評論也需要?jiǎng)h除,否則這些評論還會(huì)占用數(shù)據(jù)庫空間。在 Rails 中可以使用關(guān)聯(lián)的 dependent 選項(xiàng)來完成這一工作。像下面這樣修改 app/models/article.rb 文件中的 Article 模型:

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

9 安全

9.1 基本身份驗(yàn)證

現(xiàn)在如果我們把博客放在網(wǎng)上,任何人都能夠添加、修改、刪除文章或刪除評論。

Rails 提供了一個(gè)非常簡單的 HTTP 身份驗(yàn)證系統(tǒng),可以很好地解決這個(gè)問題。

我們需要一種方法來禁止未認(rèn)證用戶訪問 ArticlesController 的動(dòng)作。這里我們可以使用 Rails 的 http_basic_authenticate_with 方法,通過這個(gè)方法的認(rèn)證后才能訪問所請求的動(dòng)作。

要使用這個(gè)身份驗(yàn)證系統(tǒng),可以在 app/controllers/articles_controller 文件中的 ArticlesController 的頂部進(jìn)行指定。這里除了 indexshow 動(dòng)作,其他動(dòng)作都要通過身份驗(yàn)證才能訪問,為此要像下面這樣添加代碼:

class ArticlesController < ApplicationController
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
  def index
    @articles = Article.all
  end
  # 為了行文簡潔,省略以下內(nèi)容

同時(shí)只有通過身份驗(yàn)證的用戶才能刪除評論,為此要在 CommentsControllerapp/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
  # 為了行文簡潔,省略以下內(nèi)容

現(xiàn)在如果我們試著新建文章,就會(huì)看到 HTTP 基本身份驗(yàn)證對話框:

HTTP 基本認(rèn)證對話框

此外,還可以在 Rails 中使用其他身份驗(yàn)證方法。在眾多選擇中,DeviseAuthlogic 是兩個(gè)流行的 Rails 身份驗(yàn)證擴(kuò)展。

9.2 其他安全注意事項(xiàng)

安全,尤其是 Web 應(yīng)用的安全,是一個(gè)廣泛和值得深入研究的領(lǐng)域。關(guān)于 Rails 應(yīng)用安全的更多介紹,請參閱安全指南

10 接下來做什么?

至此,我們已經(jīng)完成了第一個(gè) Rails 應(yīng)用,請?jiān)诖嘶A(chǔ)上盡情修改、試驗(yàn)。

記住你不需要獨(dú)自完成一切,在安裝和運(yùn)行 Rails 時(shí)如果需要幫助,請隨時(shí)使用下面的資源:

11 配置問題

在 Rails 中,儲存外部數(shù)據(jù)最好都使用 UTF-8 編碼。雖然 Ruby 庫和 Rails 通常都能將使用其他編碼的外部數(shù)據(jù)轉(zhuǎn)換為 UTF-8 編碼,但并非總是能可靠地工作,所以最好還是確保所有的外部數(shù)據(jù)都使用 UTF-8 編碼。

編碼出錯(cuò)的最常見癥狀是在瀏覽器中出現(xiàn)帶有問號的黑色菱形塊,另一個(gè)常見癥狀是本該出現(xiàn)“ü”字符的地方出現(xiàn)了“??”字符。Rails 內(nèi)部采取了許多步驟來解決常見的可以自動(dòng)檢測和糾正的編碼問題。盡管如此,如果不使用 UTF-8 編碼來儲存外部數(shù)據(jù),偶爾還是會(huì)出現(xiàn)無法自動(dòng)檢測和糾正的編碼問題。

下面是非 UTF-8 編碼數(shù)據(jù)的兩種常見來源:

  • 文本編輯器:大多數(shù)文本編輯器(例如 TextMate)默認(rèn)使用 UTF-8 編碼保存文件。如果你的文本編輯器未使用 UTF-8 編碼,就可能導(dǎo)致在模板中輸入的特殊字符(例如 é)在瀏覽器中顯示為帶有問號的黑色菱形塊。這個(gè)問題也會(huì)出現(xiàn)在 i18n 翻譯文件中。大多數(shù)未默認(rèn)使用 UTF-8 編碼的文本編輯器(例如 Dreamweaver 的某些版本)提供了將默認(rèn)編碼修改為 UTF-8 的方法,別忘了進(jìn)行修改。

  • 數(shù)據(jù)庫:默認(rèn)情況下,Rails 會(huì)把從數(shù)據(jù)庫中取出的數(shù)據(jù)轉(zhuǎn)換成 UTF-8 格式。盡管如此,如果數(shù)據(jù)庫內(nèi)部不使用 UTF-8 編碼,就有可能無法保存用戶輸入的所有字符。例如,如果數(shù)據(jù)庫內(nèi)部使用 Latin-1 編碼,而用戶輸入了俄語、希伯來語或日語字符,那么在把數(shù)據(jù)保存到數(shù)據(jù)庫時(shí)就會(huì)造成數(shù)據(jù)永久丟失。因此,只要可能,就請?jiān)跀?shù)據(jù)庫內(nèi)部使用 UTF-8 編碼。

反饋

我們鼓勵(lì)您幫助提高本指南的質(zhì)量。

如果看到如何錯(cuò)字或錯(cuò)誤,請反饋給我們。 您可以閱讀我們的文檔貢獻(xiàn)指南。

您還可能會(huì)發(fā)現(xiàn)內(nèi)容不完整或不是最新版本。 請?zhí)砑尤笔臋n到 master 分支。請先確認(rèn) Edge Guides 是否已經(jīng)修復(fù)。 關(guān)于用語約定,請查看Ruby on Rails 指南指導(dǎo)。

無論什么原因,如果你發(fā)現(xiàn)了問題但無法修補(bǔ)它,請創(chuàng)建 issue。

最后,歡迎到 rubyonrails-docs 郵件列表參與任何有關(guān) Ruby on Rails 文檔的討論。

中文翻譯反饋

貢獻(xiàn):https://github.com/ruby-china/guides。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产亚洲精品久久久优势| 日韩特级黄片免费观看| 一区二区三区人妻在线| 情一色一区二区三区四| 欧美日韩免费黄片观看| 91人妻人人精品人人爽| 色婷婷丁香激情五月天| 欧美日韩无卡一区二区| 翘臀少妇成人一区二区| 亚洲国产欧美精品久久| 美女黄色三级深夜福利| 日本午夜福利视频免费观看| 果冻传媒在线观看免费高清| 欧美性欧美一区二区三区| 国产人妻熟女高跟丝袜| 久久热九九这里只有精品| 国产精品免费自拍视频| 精品高清美女精品国产区| 四季精品人妻av一区二区三区 | 十八禁日本一区二区三区| 加勒比人妻精品一区二区| 亚洲欧美国产中文色妇| 久久精品国产99精品最新| 日韩午夜老司机免费视频| 久久精品国产一区久久久| 国产亚洲欧美一区二区| 欧美国产日本高清在线| 亚洲精品福利视频在线观看| 国产一区二区在线免费| 中文字幕亚洲视频一区二区| 国产黄色高清内射熟女视频| 不卡视频免费一区二区三区| 亚洲国产成人精品福利| 老司机激情五月天在线不卡| 青草草在线视频免费视频| 久久福利视频在线观看| 欧美日韩一级aa大片| 亚洲高清中文字幕一区二三区| 国产91人妻精品一区二区三区 | 国产精品夜色一区二区三区不卡| 亚洲综合激情另类专区老铁性|