<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # Rails 入門 本文介紹如何開始使用 Ruby on Rails。 讀完本文,你將學到: * 如何安裝 Rails,新建 Rails 程序,如何連接數據庫; * Rails 程序的基本文件結構; * MVC(模型,視圖,控制器)和 REST 架構的基本原理; * 如何快速生成 Rails 程序骨架; ### Chapters 1. [前提條件](#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6) 2. [Rails 是什么?](#rails-%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F) 3. [新建 Rails 程序](#%E6%96%B0%E5%BB%BA-rails-%E7%A8%8B%E5%BA%8F) * [安裝 Rails](#%E5%AE%89%E8%A3%85-rails) * [創建 Blog 程序](#%E5%88%9B%E5%BB%BA-blog-%E7%A8%8B%E5%BA%8F) 4. [Hello, Rails!](#hello,-rails-bang) * [啟動服務器](#%E5%90%AF%E5%8A%A8%E6%9C%8D%E5%8A%A1%E5%99%A8) * [顯示“Hello, Rails!”](#%E6%98%BE%E7%A4%BA%E2%80%9Chello,-rails-bang%E2%80%9D) * [設置程序的首頁](#%E8%AE%BE%E7%BD%AE%E7%A8%8B%E5%BA%8F%E7%9A%84%E9%A6%96%E9%A1%B5) 5. [開始使用](#%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8) * [挖地基](#%E6%8C%96%E5%9C%B0%E5%9F%BA) * [首個表單](#%E9%A6%96%E4%B8%AA%E8%A1%A8%E5%8D%95) * [創建文章](#%E5%88%9B%E5%BB%BA%E6%96%87%E7%AB%A0) * [創建 Article 模型](#%E5%88%9B%E5%BB%BA-article-%E6%A8%A1%E5%9E%8B) * [運行遷移](#%E8%BF%90%E8%A1%8C%E8%BF%81%E7%A7%BB) * [在控制器中保存數據](#%E5%9C%A8%E6%8E%A7%E5%88%B6%E5%99%A8%E4%B8%AD%E4%BF%9D%E5%AD%98%E6%95%B0%E6%8D%AE) * [顯示文章](#%E6%98%BE%E7%A4%BA%E6%96%87%E7%AB%A0) * [列出所有文章](#%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89%E6%96%87%E7%AB%A0) * [添加鏈接](#%E6%B7%BB%E5%8A%A0%E9%93%BE%E6%8E%A5) * [添加數據驗證](#%E6%B7%BB%E5%8A%A0%E6%95%B0%E6%8D%AE%E9%AA%8C%E8%AF%81) * [更新文章](#%E6%9B%B4%E6%96%B0%E6%96%87%E7%AB%A0) * [使用局部視圖去掉視圖中的重復代碼](#%E4%BD%BF%E7%94%A8%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E5%8E%BB%E6%8E%89%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E4%BB%A3%E7%A0%81) * [刪除文章](#%E5%88%A0%E9%99%A4%E6%96%87%E7%AB%A0) 6. [添加第二個模型](#%E6%B7%BB%E5%8A%A0%E7%AC%AC%E4%BA%8C%E4%B8%AA%E6%A8%A1%E5%9E%8B) * [生成模型](#%E7%94%9F%E6%88%90%E6%A8%A1%E5%9E%8B) * [模型關聯](#%E6%A8%A1%E5%9E%8B%E5%85%B3%E8%81%94) * [添加評論的路由](#%E6%B7%BB%E5%8A%A0%E8%AF%84%E8%AE%BA%E7%9A%84%E8%B7%AF%E7%94%B1) * [生成控制器](#%E7%94%9F%E6%88%90%E6%8E%A7%E5%88%B6%E5%99%A8) 7. [重構](#%E9%87%8D%E6%9E%84) * [渲染局部視圖中的集合](#%E6%B8%B2%E6%9F%93%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E9%9B%86%E5%90%88) * [渲染局部視圖中的表單](#%E6%B8%B2%E6%9F%93%E5%B1%80%E9%83%A8%E8%A7%86%E5%9B%BE%E4%B8%AD%E7%9A%84%E8%A1%A8%E5%8D%95) 8. [刪除評論](#%E5%88%A0%E9%99%A4%E8%AF%84%E8%AE%BA) * [刪除關聯對象](#%E5%88%A0%E9%99%A4%E5%85%B3%E8%81%94%E5%AF%B9%E8%B1%A1) 9. [安全](#%E5%AE%89%E5%85%A8) * [基本認證](#%E5%9F%BA%E6%9C%AC%E8%AE%A4%E8%AF%81) * [其他安全注意事項](#%E5%85%B6%E4%BB%96%E5%AE%89%E5%85%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9) 10. [接下來做什么](#%E6%8E%A5%E4%B8%8B%E6%9D%A5%E5%81%9A%E4%BB%80%E4%B9%88) 11. [常見問題](#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) ### 1 前提條件 本文針對想從零開始開發 Rails 程序的初學者,不需要預先具備任何的 Rails 使用經驗。不過,為了能順利閱讀,還是需要事先安裝好一些軟件: * [Ruby](https://www.ruby-lang.org/en/downloads) 1.9.3 及以上版本 * 包管理工具 [RubyGems](https://rubygems.org),隨 Ruby 1.9+ 安裝。想深入了解 RubyGems,請閱讀 [RubyGems 指南](http://guides.rubygems.org) * [SQLite3](https://www.sqlite.org) 數據庫 Rails 是使用 Ruby 語言開發的網頁程序框架。如果之前沒接觸過 Ruby,學習 Rails 可要深下一番功夫。網上有很多資源可以學習 Ruby: * [Ruby 語言官方網站](https://www.ruby-lang.org/zh_cn/documentation/) * [reSRC 列出的免費編程書籍](http://resrc.io/list/10/list-of-free-programming-books/#ruby) 記住,某些資源雖然很好,但是針對 Ruby 1.8,甚至 1.6 編寫的,所以沒有介紹一些 Rails 日常開發會用到的句法。 ### 2 Rails 是什么? Rails 是使用 Ruby 語言編寫的網頁程序開發框架,目的是為開發者提供常用組件,簡化網頁程序的開發。只需編寫較少的代碼,就能實現其他編程語言或框架難以企及的功能。經驗豐富的 Rails 程序員會發現,Rails 讓程序開發變得更有樂趣。 Rails 有自己的一套規則,認為問題總有最好的解決方法,而且建議使用最好的方法,有些情況下甚至不推薦使用其他替代方案。學會如何按照 Rails 的思維開發,能極大提高開發效率。如果堅持在 Rails 開發中使用其他語言中的舊思想,嘗試使用別處學來的編程模式,開發過程就不那么有趣了。 Rails 哲學包含兩大指導思想: * **不要自我重復(DRY):** DRY 是軟件開發中的一個原則,“系統中的每個功能都要具有單一、準確、可信的實現。”。不重復表述同一件事,寫出的代碼才能更易維護,更具擴展性,也更不容易出問題。 * **多約定,少配置:** Rails 為網頁程序的大多數需求都提供了最好的解決方法,而且默認使用這些約定,不用在長長的配置文件中設置每個細節。 ### 3 新建 Rails 程序 閱讀本文時,最佳方式是跟著一步一步操作,如果錯過某段代碼或某個步驟,程序就可能出錯,所以請一步一步跟著做。 本文會新建一個名為 `blog` 的 Rails 程序,這是一個非常簡單的博客。在開始開發程序之前,要確保已經安裝了 Rails。 文中的示例代碼使用 `$` 表示命令行提示符,你的提示符可能修改過,所以會不一樣。在 Windows 中,提示符可能是 `c:\source_code&gt;`。 #### 3.1 安裝 Rails 打開命令行:在 Mac OS X 中打開 Terminal.app,在 Windows 中選擇“運行”,然后輸入“cmd.exe”。下文中所有以 `$` 開頭的代碼,都要在命令行中運行。先確認是否安裝了 Ruby 最新版: 有很多工具可以幫助你快速在系統中安裝 Ruby 和 Ruby on Rails。Windows 用戶可以使用 [Rails Installer](http://railsinstaller.org),Mac OS X 用戶可以使用 [Tokaido](https://github.com/tokaido/tokaidoapp)。 ``` $ ruby -v ruby 2.1.2p95 ``` 如果你還沒安裝 Ruby,請訪問 [ruby-lang.org](https://www.ruby-lang.org/en/downloads/),找到針對所用系統的安裝方法。 很多類 Unix 系統都自帶了版本尚新的 SQLite3。Windows 等其他操作系統的用戶可以在 [SQLite3 的網站](https://www.sqlite.org)上找到安裝說明。然后,確認是否在 PATH 中: ``` $ sqlite3 --version ``` 命令行應該回顯版本才對。 安裝 Rails,請使用 RubyGems 提供的 `gem install` 命令: ``` $ gem install rails ``` 要檢查所有軟件是否都正確安裝了,可以執行下面的命令: ``` $ rails --version ``` 如果顯示的結果類似“Rails 4.2.0”,那么就可以繼續往下讀了。 #### 3.2 創建 Blog 程序 Rails 提供了多個被稱為“生成器”的腳本,可以簡化開發,生成某項操作需要的所有文件。其中一個是新程序生成器,生成一個 Rails 程序骨架,不用自己一個一個新建文件。 打開終端,進入有寫權限的文件夾,執行以下命令生成一個新程序: ``` $ rails new blog ``` 這個命令會在文件夾 `blog` 中新建一個 Rails 程序,然后執行 `bundle install` 命令安裝 `Gemfile` 中列出的 gem。 執行 `rails new -h` 可以查看新程序生成器的所有命令行選項。 生成 `blog` 程序后,進入該文件夾: ``` $ cd blog ``` `blog` 文件夾中有很多自動生成的文件和文件夾,組成一個 Rails 程序。本文大部分時間都花在 `app` 文件夾上。下面簡單介紹默認生成的文件和文件夾的作用: | 文件/文件夾 | 作用 | | --- | --- | | app/ | 存放程序的控制器、模型、視圖、幫助方法、郵件和靜態資源文件。本文主要關注的是這個文件夾。 | | bin/ | 存放運行程序的 `rails` 腳本,以及其他用來部署或運行程序的腳本。 | | config/ | 設置程序的路由,數據庫等。詳情參閱“[設置 Rails 程序](/configuring.html)”一文。 | | config.ru | 基于 Rack 服務器的程序設置,用來啟動程序。 | | db/ | 存放當前數據庫的模式,以及數據庫遷移文件。 | | Gemfile, Gemfile.lock | 這兩個文件用來指定程序所需的 gem 依賴件,用于 Bundler gem。關于 Bundler 的詳細介紹,請訪問 [Bundler 官網](http://bundler.io)。 | | lib/ | 程序的擴展模塊。 | | log/ | 程序的日志文件。 | | public/ | 唯一對外開放的文件夾,存放靜態文件和編譯后的資源文件。 | | Rakefile | 保存并加載可在命令行中執行的任務。任務在 Rails 的各組件中定義。如果想添加自己的任務,不要修改這個文件,把任務保存在 `lib/tasks` 文件夾中。 | | README.rdoc | 程序的簡單說明。你應該修改這個文件,告訴其他人這個程序的作用,如何安裝等。 | | test/ | 單元測試,固件等測試用文件。詳情參閱“[測試 Rails 程序](/testing.html)”一文。 | | tmp/ | 臨時文件,例如緩存,PID,會話文件。 | | vendor/ | 存放第三方代碼。經常用來放第三方 gem。 | ### 4 Hello, Rails! 首先,我們來添加一些文字,在頁面中顯示。為了能訪問網頁,要啟動程序服務器。 #### 4.1 啟動服務器 現在,新建的 Rails 程序已經可以正常運行。要訪問網站,需要在開發電腦上啟動服務器。請在 `blog` 文件夾中執行下面的命令: ``` $ rails server ``` 把 CoffeeScript 編譯成 JavaScript 需要 JavaScript 運行時,如果沒有運行時,會報錯,提示沒有 `execjs`。Mac OS X 和 Windows 一般都提供了 JavaScript 運行時。Rails 生成的 `Gemfile` 中,安裝 `therubyracer` gem 的代碼被注釋掉了,如果需要使用這個 gem,請把前面的注釋去掉。在 JRuby 中推薦使用 `therubyracer`。在 JRuby 中生成的 `Gemfile` 已經包含了這個 gem。所有支持的運行時參見 [ExecJS](https://github.com/sstephenson/execjs#readme)。 上述命令會啟動 WEBrick,這是 Ruby 內置的服務器。要查看程序,請打開一個瀏覽器窗口,訪問 [http://localhost:3000](http://localhost:3000)。應該會看到默認的 Rails 信息頁面: ![歡迎使用頁面](https://box.kancloud.cn/2016-05-11_5732ab0bd70e2.png) 要想停止服務器,請在命令行中按 Ctrl+C 鍵。服務器成功停止后回重新看到命令行提示符。在大多數類 Unix 系統中,包括 Mac OS X,命令行提示符是 `$` 符號。在開發模式中,一般情況下無需重啟服務器,修改文件后,服務器會自動重新加載。 “歡迎使用”頁面是新建 Rails 程序后的“冒煙測試”:確保程序設置正確,能順利運行。你可以點擊“About your application's environment”鏈接查看程序所處環境的信息。 #### 4.2 顯示“Hello, Rails!” 要在 Rails 中顯示“Hello, Rails!”,需要新建一個控制器和視圖。 控制器用來接受向程序發起的請求。路由決定哪個控制器會接受到這個請求。一般情況下,每個控制器都有多個路由,對應不同的動作。動作用來提供視圖中需要的數據。 視圖的作用是,以人類能看懂的格式顯示數據。有一點要特別注意,數據是在控制器中獲取的,而不是在視圖中。視圖只是把數據顯示出來。默認情況下,視圖使用 eRuby(嵌入式 Ruby)語言編寫,經由 Rails 解析后,再發送給用戶。 控制器可用控制器生成器創建,你要告訴生成器,我想要個名為“welcome”的控制器和一個名為“index”的動作,如下所示: ``` $ rails generate controller welcome index ``` 運行上述命令后,Rails 會生成很多文件,以及一個路由。 ``` create app/controllers/welcome_controller.rb route get 'welcome/index' invoke erb create app/views/welcome create app/views/welcome/index.html.erb invoke test_unit create test/controllers/welcome_controller_test.rb invoke helper create app/helpers/welcome_helper.rb invoke assets invoke coffee create app/assets/javascripts/welcome.js.coffee invoke scss create app/assets/stylesheets/welcome.css.scss ``` 在這些文件中,最重要的當然是控制器,位于 `app/controllers/welcome_controller.rb`,以及視圖,位于 `app/views/welcome/index.html.erb`。 使用文本編輯器打開 `app/views/welcome/index.html.erb` 文件,刪除全部內容,寫入下面這行代碼: ``` <h1>Hello, Rails!</h1> ``` #### 4.3 設置程序的首頁 我們已經創建了控制器和視圖,現在要告訴 Rails 在哪個地址上顯示“Hello, Rails!”。這里,我們希望訪問根地址 [http://localhost:3000](http://localhost:3000) 時顯示。但是現在顯示的還是歡迎頁面。 我們要告訴 Rails 真正的首頁是什么。 在編輯器中打開 `config/routes.rb` 文件。 ``` Rails.application.routes.draw do get 'welcome/index' # The priority is based upon order of creation: # first created -> highest priority. # # You can have the root of your site routed with "root" # root 'welcome#index' # # ... ``` 這是程序的路由文件,使用特殊的 DSL(domain-specific language,領域專屬語言)編寫,告知 Rails 請求應該發往哪個控制器和動作。文件中有很多注釋,舉例說明如何定義路由。其中有一行說明了如何指定控制器和動作設置網站的根路由。找到以 `root` 開頭的代碼行,去掉注釋,變成這樣: ``` root 'welcome#index' ``` `root 'welcome#index'` 告知 Rails,訪問程序的根路徑時,交給 `welcome` 控制器中的 `index` 動作處理。`get 'welcome/index'` 告知 Rails,訪問 [http://localhost:3000/welcome/index](http://localhost:3000/welcome/index) 時,交給 `welcome` 控制器中的 `index` 動作處理。`get 'welcome/index'` 是運行 `rails generate controller welcome index` 時生成的。 如果生成控制器時停止了服務器,請再次啟動(`rails server`),然后在瀏覽器中訪問 [http://localhost:3000](http://localhost:3000)。你會看到之前寫入 `app/views/welcome/index.html.erb` 文件的“Hello, Rails!”,說明新定義的路由把根目錄交給 `WelcomeController` 的 `index` 動作處理了,而且也正確的渲染了視圖。 關于路由的詳細介紹,請閱讀“[Rails 路由全解](/routing.html)”一文。 ### 5 開始使用 前文已經介紹如何創建控制器、動作和視圖,下面我們來創建一些更實質的功能。 在博客程序中,我們要創建一個新“資源”。資源是指一系列類似的對象,比如文章,人和動物。 資源可以被創建、讀取、更新和刪除,這些操作簡稱 CRUD。 Rails 提供了一個 `resources` 方法,可以聲明一個符合 REST 架構的資源。創建文章資源后,`config/routes.rb` 文件的內容如下: ``` Rails.application.routes.draw do resources :articles root 'welcome#index' end ``` 執行 `rake routes` 任務,會看到定義了所有標準的 REST 動作。輸出結果中各列的意義稍后會說明,現在只要留意 `article` 的單復數形式,這在 Rails 中有特殊的含義。 ``` $ bin/rake routes Prefix Verb URI Pattern Controller#Action articles GET /articles(.:format) articles#index POST /articles(.:format) articles#create new_article GET /articles/new(.:format) articles#new edit_article GET /articles/:id/edit(.:format) articles#edit article GET /articles/:id(.:format) articles#show PATCH /articles/:id(.:format) articles#update PUT /articles/:id(.:format) articles#update DELETE /articles/:id(.:format) articles#destroy root GET / welcome#index ``` 下一節,我們會加入新建文章和查看文章的功能。這兩個操作分別對應于 CRUD 的 C 和 R,即創建和讀取。新建文章的表單如下所示: ![新建文章表單](https://box.kancloud.cn/2016-05-11_5732ab0d7adff.png) 表單看起來很簡陋,不過沒關系,后文會加入更多的樣式。 #### 5.1 挖地基 首先,程序中要有個頁面用來新建文章。一個比較好的選擇是 `/articles/new`。這個路由前面已經定義了,可以訪問。打開 [http://localhost:3000/articles/new](http://localhost:3000/articles/new) ,會看到如下的路由錯誤: ![路由錯誤,常量 ArticlesController 未初始化](https://box.kancloud.cn/2016-05-11_5732ab0d8bc68.png) 產生這個錯誤的原因是,沒有定義用來處理該請求的控制器。解決這個問題的方法很簡單,執行下面的命令創建名為 `ArticlesController` 的控制器即可: ``` $ bin/rails g controller articles ``` 打開剛生成的 `app/controllers/articles_controller.rb` 文件,會看到一個幾乎沒什么內容的控制器: ``` class ArticlesController < ApplicationController end ``` 控制器就是一個類,繼承自 `ApplicationController`。在這個類中定義的方法就是控制器的動作。動作的作用是處理文章的 CRUD 操作。 在 Ruby 中,方法分為 `public`、`private` 和 `protected` 三種,只有 `public` 方法才能作為控制器的動作。詳情參閱 [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/) 一書。 現在刷新 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),會看到一個新錯誤: ![ArticlesController 控制器不知如何處理 new 動作](https://box.kancloud.cn/2016-05-11_5732ab0d9d063.png) 這個錯誤的意思是,在剛生成的 `ArticlesController` 控制器中找不到 `new` 動作。因為在生成控制器時,除非指定要哪些動作,否則不會生成,控制器是空的。 手動創建動作只需在控制器中定義一個新方法。打開 `app/controllers/articles_controller.rb` 文件,在 `ArticlesController` 類中,定義 `new` 方法,如下所示: ``` class ArticlesController < ApplicationController def new end end ``` 在 `ArticlesController` 中定義 `new` 方法后,再刷新 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),看到的還是個錯誤: ![找不到 articles/new 所用模板](https://box.kancloud.cn/2016-05-11_5732ab0db766b.png) 產生這個錯誤的原因是,Rails 希望這樣的常規動作有對應的視圖,用來顯示內容。沒有視圖可用,Rails 就報錯了。 在上圖中,最后一行被截斷了,我們來看一下完整的信息: ``` Missing template articles/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" ``` 這行信息還挺長,我們來看一下到底是什么意思。 第一部分說明找不到哪個模板,這里,丟失的是 `articles/new` 模板。Rails 首先會尋找這個模板,如果找不到,再找名為 `application/new` 的模板。之所以這么找,是因為 `ArticlesController` 繼承自 `ApplicationController`。 后面一部分是個 Hash。`:locale` 表示要找哪國語言模板,默認是英語(`"en"`)。`:format` 表示響應使用的模板格式,默認為 `:html`,所以 Rails 要尋找一個 HTML 模板。`:handlers` 表示用來處理模板的程序,HTML 模板一般使用 `:erb`,XML 模板使用 `:builder`,`:coffee` 用來把 CoffeeScript 轉換成 JavaScript。 最后一部分說明 Rails 在哪里尋找模板。在這個簡單的程序里,模板都存放在一個地方,復雜的程序可能存放在多個位置。 讓這個程序正常運行,最簡單的一種模板是 `app/views/articles/new.html.erb`。模板文件的擴展名是關鍵所在:第一個擴展名是模板的類型,第二個擴展名是模板的處理程序。Rails 會嘗試在 `app/views` 文件夾中尋找名為 `articles/new` 的模板。這個模板的類型只能是 `html`,處理程序可以是 `erb`、`builder` 或 `coffee`。因為我們要編寫一個 HTML 表單,所以使用 `erb`。所以這個模板文件應該命名為 `articles/new.html.erb`,還要放在 `app/views` 文件夾中。 新建文件 `app/views/articles/new.html.erb`,寫入如下代碼: ``` <h1>New Article</h1> ``` 再次刷新 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),可以看到頁面中顯示了一個標頭。現在路由、控制器、動作和視圖都能正常運行了。接下來要編寫新建文章的表單了。 #### 5.2 首個表單 要在模板中編寫表單,可以使用“表單構造器”。Rails 中常用的表單構造器是 `form_for`。在 `app/views/articles/new.html.erb` 文件中加入以下代碼: ``` <%= form_for :article do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> </p> <p> <%= f.label :text %><br> <%= f.text_area :text %> </p> <p> <%= f.submit %> </p> <% end %> ``` 現在刷新頁面,會看到上述代碼生成的表單。在 Rails 中編寫表單就是這么簡單! 調用 `form_for` 方法時,要指定一個對象。在上面的表單中,指定的是 `:article`。這個對象告訴 `form_for`,這個表單是用來處理哪個資源的。在 `form_for` 方法的塊中,`FormBuilder` 對象(用 `f` 表示)創建了兩個標簽和兩個文本字段,一個用于文章標題,一個用于文章內容。最后,在 `f` 對象上調用 `submit` 方法,創建一個提交按鈕。 不過這個表單還有個問題。如果查看這個頁面的源碼,會發現表單 `action` 屬性的值是 `/articles/new`。這就是問題所在,因為其指向的地址就是現在這個頁面,而這個頁面是用來顯示新建文章表單的。 要想轉到其他地址,就要使用其他的地址。這個問題可使用 `form_for` 方法的 `:url` 選項解決。在 Rails 中,用來處理新建資源表單提交數據的動作是 `create`,所以表單應該轉向這個動作。 修改 `app/views/articles/new.html.erb` 文件中的 `form_for`,改成這樣: ``` <%= form_for :article, url: articles_path do |f| %> ``` 這里,我們把 `:url` 選項的值設為 `articles_path` 幫助方法。要想知道這個方法有什么作用,我們要回過頭再看一下 `rake routes` 的輸出: ``` $ bin/rake routes Prefix Verb URI Pattern Controller#Action articles GET /articles(.:format) articles#index POST /articles(.:format) articles#create new_article GET /articles/new(.:format) articles#new edit_article GET /articles/:id/edit(.:format) articles#edit article GET /articles/:id(.:format) articles#show PATCH /articles/:id(.:format) articles#update PUT /articles/:id(.:format) articles#update DELETE /articles/:id(.:format) articles#destroy root GET / welcome#index ``` `articles_path` 幫助方法告訴 Rails,對應的地址是 `/articles`,默認情況下,這個表單會向這個路由發起 `POST` 請求。這個路由對應于 `ArticlesController` 控制器的 `create` 動作。 表單寫好了,路由也定義了,現在可以填寫表單,然后點擊提交按鈕新建文章了。請實際操作一下。提交表單后,會看到一個熟悉的錯誤: ![ArticlesController 控制器不知如何處理 create 動作](https://box.kancloud.cn/2016-05-11_5732ab0dcb923.png) 解決這個錯誤,要在 `ArticlesController` 控制器中定義 `create` 動作。 #### 5.3 創建文章 要解決前一節出現的錯誤,可以在 `ArticlesController` 類中定義 `create` 方法。在 `app/controllers/articles_controller.rb` 文件中 `new` 方法后面添加以下代碼: ``` class ArticlesController < ApplicationController def new end def create end end ``` 然后再次提交表單,會看到另一個熟悉的錯誤:找不到模板。現在暫且不管這個錯誤。`create` 動作的作用是把新文章保存到數據庫中。 提交表單后,其中的字段以參數的形式傳遞給 Rails。這些參數可以在控制器的動作中使用,完成指定的操作。要想查看這些參數的內容,可以把 `create` 動作改成: ``` def create render plain: params[:article].inspect end ``` `render` 方法接受一個簡單的 Hash 為參數,這個 Hash 的鍵是 `plain`,對應的值為 `params[:article].inspect`。`params` 方法表示通過表單提交的參數,返回 `ActiveSupport::HashWithIndifferentAccess` 對象,可以使用字符串或者 Symbol 獲取鍵對應的值。現在,我們只關注通過表單提交的參數。 如果現在再次提交表單,不會再看到找不到模板錯誤,而是會看到類似下面的文字: ``` {"title"=>"First article!", "text"=>"This is my first article."} ``` `create` 動作把表單提交的參數顯示出來了。不過這么做沒什么用,看到了參數又怎樣,什么都沒發生。 #### 5.4 創建 Article 模型 在 Rails 中,模型的名字使用單數,對應的數據表名使用復數。Rails 提供了一個生成器用來創建模型,大多數 Rails 開發者創建模型時都會使用。創建模型,請在終端里執行下面的命令: ``` $ bin/rails generate model Article title:string text:text ``` 這個命令告知 Rails,我們要創建 `Article` 模型,以及一個字符串屬性 `title` 和文本屬性 `text`。這兩個屬性會自動添加到 `articles` 數據表中,映射到 `Article` 模型。 執行這個命令后,Rails 會生成一堆文件。現在我們只關注 `app/models/article.rb` 和 `db/migrate/20140120191729_create_articles.rb`(你得到的文件名可能有點不一樣)這兩個文件。后者用來創建數據庫結構,下一節會詳細說明。 Active Record 很智能,能自動把數據表中的字段映射到模型的屬性上。所以無需在 Rails 的模型中聲明屬性,因為 Active Record 會自動映射。 #### 5.5 運行遷移 如前文所述,`rails generate model` 命令會在 `db/migrate` 文件夾中生成一個數據庫遷移文件。遷移是一個 Ruby 類,能簡化創建和修改數據庫結構的操作。Rails 使用 rake 任務運行遷移,修改數據庫結構后還能撤銷操作。遷移的文件名中有個時間戳,這樣能保證遷移按照創建的時間順序運行。 `db/migrate/20140120191729_create_articles.rb`(還記得嗎,你的遷移文件名可能有點不一樣)文件的內容如下所示: ``` class CreateArticles < ActiveRecord::Migration def change create_table :articles do |t| t.string :title t.text :text t.timestamps end end end ``` 在這個遷移中定義了一個名為 `change` 的方法,在運行遷移時執行。`change` 方法中定義的操作都是可逆的,Rails 知道如何撤銷這次遷移操作。運行遷移后,會創建 `articles` 表,以及一個字符串字段和文本字段。同時還會創建兩個時間戳字段,用來跟蹤記錄的創建時間和更新時間。 關于遷移的詳細說明,請參閱“[Active Record 數據庫遷移](/migrations.html)”一文。 然后,使用 rake 命令運行遷移: ``` $ bin/rake db:migrate ``` Rails 會執行遷移操作,告訴你創建了 `articles` 表。 ``` == CreateArticles: migrating ================================================== -- create_table(:articles) -> 0.0019s == CreateArticles: migrated (0.0020s) ========================================= ``` 因為默認情況下,程序運行在開發環境中,所以相關的操作應用于 `config/database.yml` 文件中 `development` 區域設置的數據庫上。如果想在其他環境中運行遷移,必須在命令中指明:`rake db:migrate RAILS_ENV=production`。 #### 5.6 在控制器中保存數據 再回到 `ArticlesController` 控制器,我們要修改 `create` 動作,使用 `Article` 模型把數據保存到數據庫中。打開 `app/controllers/articles_controller.rb` 文件,把 `create` 動作修改成這樣: ``` def create @article = Article.new(params[:article]) @article.save redirect_to @article end ``` 在 Rails 中,每個模型可以使用各自的屬性初始化,自動映射到數據庫字段上。`create` 動作中的第一行就是這個目的(還記得嗎,`params[:article]` 就是我們要獲取的屬性)。`@article.save` 的作用是把模型保存到數據庫中。保存完后轉向 `show` 動作。稍后再編寫 `show` 動作。 后文會看到,`@article.save` 返回一個布爾值,表示保存是否成功。 再次訪問 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),填寫表單,還差一步就能創建文章了,會看到一個錯誤頁面: ![新建文章時禁止使用屬性](https://box.kancloud.cn/2016-05-11_5732ab0ddfa1e.png) Rails 提供了很多安全防范措施保證程序的安全,你所看到的錯誤就是因為違反了其中一個措施。這個防范措施叫做“健壯參數”,我們要明確地告知 Rails 哪些參數可在控制器中使用。這里,我們想使用 `title` 和 `text` 參數。請把 `create` 動作修改成: ``` def create @article = Article.new(article_params) @article.save redirect_to @article end private def article_params params.require(:article).permit(:title, :text) end ``` 看到 `permit` 方法了嗎?這個方法允許在動作中使用 `title` 和 `text` 屬性。 注意,`article_params` 是私有方法。這種用法可以防止攻擊者把修改后的屬性傳遞給模型。關于健壯參數的更多介紹,請閱讀[這篇文章](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/)。 #### 5.7 顯示文章 現在再次提交表單,Rails 會提示找不到 `show` 動作。這個提示沒多大用,我們還是先添加 `show` 動作吧。 我們在 `rake routes` 的輸出中看到,`show` 動作的路由是: ``` article GET /articles/:id(.:format) articles#show ``` `:id` 的意思是,路由期望接收一個名為 `id` 的參數,在這個例子中,就是文章的 ID。 和前面一樣,我們要在 `app/controllers/articles_controller.rb` 文件中添加 `show` 動作,以及相應的視圖文件。 ``` def show @article = Article.find(params[:id]) end ``` 有幾點要注意。我們調用 `Article.find` 方法查找想查看的文章,傳入的參數 `params[:id]` 會從請求中獲取 `:id` 參數。我們還把文章對象存儲在一個實例變量中(以 `@` 開頭的變量),只有這樣,變量才能在視圖中使用。 然后,新建 `app/views/articles/show.html.erb` 文件,寫入下面的代碼: ``` <p> <strong>Title:</strong> <%= @article.title %> </p> <p> <strong>Text:</strong> <%= @article.text %> </p> ``` 做了以上修改后,就能真正的新建文章了。訪問 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),自己試試。 ![顯示文章](https://box.kancloud.cn/2016-05-11_5732ab0e006bc.png) #### 5.8 列出所有文章 我們還要列出所有文章,對應的路由是: ``` articles GET /articles(.:format) articles#index ``` 在 `app/controllers/articles_controller.rb` 文件中,為 `ArticlesController` 控制器添加 `index` 動作: ``` def index @articles = Article.all end ``` 然后編寫這個動作的視圖,保存為 `app/views/articles/index.html.erb`: ``` <h1>Listing articles</h1> <table> <tr> <th>Title</th> <th>Text</th> </tr> <% @articles.each do |article| %> <tr> <td><%= article.title %></td> <td><%= article.text %></td> </tr> <% end %> </table> ``` 現在訪問 [http://localhost:3000/articles](http://localhost:3000/articles),會看到已經發布的文章列表。 #### 5.9 添加鏈接 至此,我們可以新建、顯示、列出文章了。下面我們添加一些鏈接,指向這些頁面。 打開 `app/views/welcome/index.html.erb` 文件,改成這樣: ``` <h1>Hello, Rails!</h1> <%= link_to 'My Blog', controller: 'articles' %> ``` `link_to` 是 Rails 內置的視圖幫助方法之一,根據提供的文本和地址創建超鏈接。這上面這段代碼中,地址是文章列表頁面。 接下來添加到其他頁面的鏈接。先在 `app/views/articles/index.html.erb` 中添加“New Article”鏈接,放在 `&lt;table&gt;` 標簽之前: ``` <%= link_to 'New article', new_article_path %> ``` 點擊這個鏈接后,會轉向新建文章的表單頁面。 然后在 `app/views/articles/new.html.erb` 中添加一個鏈接,位于表單下面,返回到 `index` 動作: ``` <%= form_for :article do |f| %> ... <% end %> <%= link_to 'Back', articles_path %> ``` 最后,在 `app/views/articles/show.html.erb` 模板中添加一個鏈接,返回 `index` 動作,這樣用戶查看某篇文章后就可以返回文章列表頁面了: ``` <p> <strong>Title:</strong> <%= @article.title %> </p> <p> <strong>Text:</strong> <%= @article.text %> </p> <%= link_to 'Back', articles_path %> ``` 如果要鏈接到同一個控制器中的動作,不用指定 `:controller` 選項,因為默認情況下使用的就是當前控制器。 在開發模式下(默認),每次請求 Rails 都會重新加載程序,因此修改之后無需重啟服務器。 #### 5.10 添加數據驗證 模型文件,比如 `app/models/article.rb`,可以簡單到只有這兩行代碼: ``` class Article < ActiveRecord::Base end ``` 文件中沒有多少代碼,不過請注意,`Article` 類繼承自 `ActiveRecord::Base`。Active Record 提供了很多功能,包括:基本的數據庫 CRUD 操作,數據驗證,復雜的搜索功能,以及多個模型之間的關聯。 Rails 為模型提供了很多方法,用來驗證傳入的數據。打開 `app/models/article.rb` 文件,修改成: ``` class Article < ActiveRecord::Base validates :title, presence: true, length: { minimum: 5 } end ``` 添加的這段代碼可以確保每篇文章都有一個標題,而且至少有五個字符。在模型中可以驗證數據是否滿足多種條件,包括:字段是否存在、是否唯一,數據類型,以及關聯對象是否存在。“[Active Record 數據驗證](/active_record_validations.html)”一文會詳細介紹數據驗證。 添加數據驗證后,如果把不滿足驗證條件的文章傳遞給 `@article.save`,會返回 `false`。打開 `app/controllers/articles_controller.rb` 文件,會發現,我們還沒在 `create` 動作中檢查 `@article.save` 的返回結果。如果保存失敗,應該再次顯示表單。為了實現這種功能,請打開 `app/controllers/articles_controller.rb` 文件,把 `new` 和 `create` 動作改成: ``` 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` 動作中添加了一個實例變量 `@article`。稍后你會知道為什么要這么做。 注意,在 `create` 動作中,如果保存失敗,調用的是 `render` 方法而不是 `redirect_to` 方法。用 `render` 方法才能在保存失敗后把 `@article` 對象傳給 `new` 動作的視圖。渲染操作和表單提交在同一次請求中完成;而 `redirect_to` 會讓瀏覽器發起一次新請求。 刷新 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),提交一個沒有標題的文章,Rails 會退回這個頁面,但這種處理方法沒多少用,你要告訴用戶哪兒出錯了。為了實現這種功能,請在 `app/views/articles/new.html.erb` 文件中檢測錯誤消息: ``` <%= 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?` 檢查是否有錯誤,如果有錯誤,使用 `@article.errors.full_messages` 顯示錯誤。 `pluralize` 是 Rails 提供的幫助方法,接受一個數字和字符串作為參數。如果數字比 1 大,字符串會被轉換成復數形式。 在 `new` 動作中加入 `@article = Article.new` 的原因是,如果不這么做,在視圖中 `@article` 的值就是 `nil`,調用 `@article.errors.any?` 時會發生錯誤。 Rails 會自動把出錯的表單字段包含在一個 `div` 中,并為其添加了一個 class:`field_with_errors`。我們可以定義一些樣式,凸顯出錯的字段。 再次訪問 [http://localhost:3000/articles/new](http://localhost:3000/articles/new),嘗試發布一篇沒有標題的文章,會看到一個很有用的錯誤提示。 ![出錯的表單](https://box.kancloud.cn/2016-05-11_5732ab0e13f67.png) #### 5.11 更新文章 我們已經說明了 CRUD 中的 CR 兩種操作。下面進入 U 部分,更新文章。 首先,要在 `ArticlesController` 中添加 `edit` 動作: ``` def edit @article = Article.find(params[:id]) end ``` 視圖中要添加一個類似新建文章的表單。新建 `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` 動作,現在還沒定義,稍后會添加。 `method: :patch` 選項告訴 Rails,提交這個表單時使用 `PATCH` 方法發送請求。根據 REST 架構,更新資源時要使用 HTTP `PATCH` 方法。 `form_for` 的第一個參數可以是對象,例如 `@article`,把對象中的字段填入表單。如果傳入一個和實例變量(`@article`)同名的 Symbol(`:article`),效果也是一樣。上面的代碼使用的就是 Symbol。詳情參見 [form_for 的文檔](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for)。 然后,要在 `app/controllers/articles_controller.rb` 中添加 `update` 動作: ``` 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` 方法用來處理對現有文章的更新操作,接收一個 Hash,包含想要修改的屬性。和之前一樣,如果更新文章出錯了,要再次顯示表單。 上面的代碼再次使用了前面為 `create` 動作定義的 `article_params` 方法。 不用把所有的屬性都提供給 `update` 動作。例如,如果使用 `@article.update(title: 'A new title')`,Rails 只會更新 `title` 屬性,不修改其他屬性。 最后,我們想在文章列表頁面,在每篇文章后面都加上一個鏈接,指向 `edit` 動作。打開 `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”鏈接: ``` ... <%= link_to 'Back', articles_path %> | <%= link_to 'Edit', edit_article_path(@article) %> ``` 下圖是文章列表頁面現在的樣子: ![在文章列表頁面顯示了編輯鏈接](https://box.kancloud.cn/2016-05-11_5732ab0e23bc6.png) #### 5.12 使用局部視圖去掉視圖中的重復代碼 編輯文章頁面和新建文章頁面很相似,顯示表單的代碼是相同的。下面使用局部視圖去掉兩個視圖中的重復代碼。按照約定,局部視圖的文件名以下劃線開頭。 關于局部視圖的詳細介紹參閱“[Layouts and Rendering in Rails](/layouts_and_rendering.html)”一文。 新建 `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` 的用法變了之外,其他代碼都和之前一樣。之所以能在兩個動作中共用一個 `form_for`,是因為 `@article` 是一個資源,對應于符合 REST 架構的路由,Rails 能自動分辨使用哪個地址和請求方法。 關于這種 `form_for` 用法的詳細說明,請查閱 [API 文檔](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style)。 下面來修改 `app/views/articles/new.html.erb` 視圖,使用新建的局部視圖,把其中的代碼全刪掉,替換成: ``` <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 刪除文章 現在介紹 CRUD 中的 D,從數據庫中刪除文章。按照 REST 架構的約定,刪除文章的路由是: ``` DELETE /articles/:id(.:format) articles#destroy ``` 刪除資源時使用 DELETE 請求。如果還使用 GET 請求,可以構建如下所示的惡意地址: ``` <a href='http://example.com/articles/1/destroy'>look at this cat!</a> ``` 刪除資源使用 DELETE 方法,路由會把請求發往 `app/controllers/articles_controller.rb` 中的 `destroy` 動作。`destroy` 動作現在還不存在,下面來添加: ``` def destroy @article = Article.find(params[:id]) @article.destroy redirect_to articles_path end ``` 想把記錄從數據庫刪除,可以在 Active Record 對象上調用 `destroy` 方法。注意,我們無需為這個動作編寫視圖,因為它會轉向 `index` 動作。 最后,在 `index` 動作的模板(`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> ``` 生成“Destroy”鏈接的 `link_to` 用法有點不一樣,第二個參數是具名路由,隨后還傳入了幾個參數。`:method` 和 `:'data-confirm'` 選項設置鏈接的 HTML5 屬性,點擊鏈接后,首先會顯示一個對話框,然后發起 DELETE 請求。這兩個操作通過 `jquery_ujs` 這個 JavaScript 腳本實現。生成程序骨架時,會自動把 `jquery_ujs` 加入程序的布局中(`app/views/layouts/application.html.erb`)。沒有這個腳本,就不會顯示確認對話框。 ![確認對話框](https://box.kancloud.cn/2016-05-11_5732ab0e35d86.png) 恭喜,現在你可以新建、顯示、列出、更新、刪除文章了。 一般情況下,Rails 建議使用資源對象,而不手動設置路由。關于路由的詳細介紹參閱“[Rails 路由全解](/routing.html)”一文。 ### 6 添加第二個模型 接下來要在程序中添加第二個模型,用來處理文章的評論。 #### 6.1 生成模型 下面要用到的生成器,和之前生成 `Article` 模型的一樣。我們要創建一個 `Comment` 模型,表示文章的評論。在終端執行下面的命令: ``` $ rails generate model Comment commenter:string body:text article:references ``` 這個命令生成四個文件: | 文件 | 作用 | | --- | --- | | db/migrate/20140120201010_create_comments.rb | 生成 comments 表所用的遷移文件(你得到的文件名稍有不同) | | app/models/comment.rb | Comment 模型文件 | | test/models/comment_test.rb | Comment 模型的測試文件 | | test/fixtures/comments.yml | 測試時使用的固件 | 首先來看一下 `app/models/comment.rb` 文件: ``` class Comment < ActiveRecord::Base belongs_to :article end ``` 文件的內容和前面的 `Article` 模型差不多,不過多了一行代碼:`belongs_to :article`。這行代碼用來建立 Active Record 關聯。下文會簡單介紹關聯。 除了模型文件,Rails 還生成了一個遷移文件,用來創建對應的數據表: ``` class CreateComments < ActiveRecord::Migration def change create_table :comments do |t| t.string :commenter t.text :body # this line adds an integer column called `article_id`. t.references :article, index: true t.timestamps end end end ``` `t.references` 這行代碼為兩個模型的關聯創建一個外鍵字段,同時還為這個字段創建了索引。下面運行這個遷移: ``` $ rake db:migrate ``` Rails 相當智能,只會執行還沒有運行的遷移,在命令行中會看到以下輸出: ``` == CreateComments: migrating ================================================= -- create_table(:comments) -> 0.0115s == CreateComments: migrated (0.0119s) ======================================== ``` #### 6.2 模型關聯 使用 Active Record 關聯可以輕易的建立兩個模型之間的關系。評論和文章之間的關聯是這樣的: * 評論屬于一篇文章 * 一篇文章有多個評論 這種關系和 Rails 用來聲明關聯的句法具有相同的邏輯。我們已經看過 `Comment` 模型中那行代碼,聲明評論屬于文章: ``` class Comment < ActiveRecord::Base belongs_to :article end ``` 我們要編輯 `app/models/article.rb` 文件,加入這層關系的另一端: ``` class Article < ActiveRecord::Base has_many :comments validates :title, presence: true, length: { minimum: 5 } end ``` 這兩行聲明能自動完成很多操作。例如,如果實例變量 `@article` 是一個文章對象,可以使用 `@article.comments` 取回一個數組,其元素是這篇文章的評論。 關于 Active Record 關聯的詳細介紹,參閱“[Active Record 關聯](/association_basics.html)”一文。 #### 6.3 添加評論的路由 和 `article` 控制器一樣,添加路由后 Rails 才知道在哪個地址上查看評論。打開 `config/routes.rb` 文件,按照下面的方式修改: ``` resources :articles do resources :comments end ``` 我們把 `comments` 放在 `articles` 中,這叫做嵌套資源,表明了文章和評論間的層級關系。 關于路由的詳細介紹,參閱“[Rails 路由全解](/routing.html)”一文。 #### 6.4 生成控制器 有了模型,下面要創建控制器了,還是使用前面用過的生成器: ``` $ rails generate controller Comments ``` 這個命令生成六個文件和一個空文件夾: | 文件/文件夾 | 作用 | | --- | --- | | app/controllers/comments_controller.rb | Comments 控制器文件 | | app/views/comments/ | 控制器的視圖存放在這個文件夾里 | | test/controllers/comments_controller_test.rb | 控制器測試文件 | | app/helpers/comments_helper.rb | 視圖幫助方法文件 | | test/helpers/comments_helper_test.rb | 幫助方法測試文件 | | app/assets/javascripts/comment.js.coffee | 控制器的 CoffeeScript 文件 | | app/assets/stylesheets/comment.css.scss | 控制器的樣式表文件 | 在任何一個博客中,讀者讀完文章后就可以發布評論。評論發布后,會轉向文章顯示頁面,查看自己的評論是否顯示出來了。所以,`CommentsController` 中要定義新建評論的和刪除垃圾評論的方法。 首先,修改顯示文章的模板(`app/views/articles/show.html.erb`),允許讀者發布評論: ``` <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 'Back', articles_path %> | <%= link_to 'Edit', edit_article_path(@article) %> ``` 上面的代碼在顯示文章的頁面添加了一個表單,調用 `CommentsController` 控制器的 `create` 動作發布評論。`form_for` 的參數是個數組,構建嵌套路由,例如 `/articles/1/comments`。 下面在 `app/controllers/comments_controller.rb` 文件中定義 `create` 方法: ``` 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 ``` 這里使用的代碼要比文章的控制器復雜得多,因為設置了嵌套關系,必須這么做評論功能才能使用。發布評論時要知道這個評論屬于哪篇文章,所以要在 `Article` 模型上調用 `find` 方法查找文章對象。 而且,這段代碼還充分利用了關聯關系生成的方法。我們在 `@article.comments` 上調用 `create` 方法,創建并保存評論。這么做能自動把評論和文章聯系起來,讓這個評論屬于這篇文章。 添加評論后,調用 `article_path(@article)` 幫助方法,轉向原來的文章頁面。前面說過,這個幫助函數調用 `ArticlesController` 的 `show` 動作,渲染 `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 Article', edit_article_path(@article) %> | <%= link_to 'Back to Articles', articles_path %> ``` 現在,可以為文章添加評論了,成功添加后,評論會在正確的位置顯示。 ![文章的評論](https://box.kancloud.cn/2016-05-11_5732ab0e55138.png) ### 7 重構 現在博客的文章和評論都能正常使用了。看一下 `app/views/articles/show.html.erb` 模板,內容太多。下面使用局部視圖重構。 #### 7.1 渲染局部視圖中的集合 首先,把顯示文章評論的代碼抽出來,寫入局部視圖中。新建 `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 Article', edit_article_path(@article) %> | <%= link_to 'Back to Articles', articles_path %> ``` 這個視圖會使用局部視圖 `app/views/comments/_comment.html.erb` 渲染 `@article.comments` 集合中的每個評論。`render` 方法會遍歷 `@article.comments` 集合,把每個評論賦值給一個和局部視圖同名的本地變量,在這個例子中本地變量是 `comment`,這個本地變量可以在局部視圖中使用。 #### 7.2 渲染局部視圖中的表單 我們把添加評論的代碼也移到局部視圖中。新建 `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 Article', edit_article_path(@article) %> | <%= link_to 'Back to Articles', articles_path %> ``` 第二個 `render` 方法的參數就是要渲染的局部視圖,即 `comments/form`。Rails 很智能,能解析其中的斜線,知道要渲染 `app/views/comments` 文件夾中的 `_form.html.erb` 模板。 `@article` 變量在所有局部視圖中都可使用,因為它是實例變量。 ### 8 刪除評論 博客還有一個重要的功能是刪除垃圾評論。為了實現這個功能,要在視圖中添加一個鏈接,并在 `CommentsController` 中定義 `destroy` 動作。 先在 `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> ``` 點擊“Destroy Comment”鏈接后,會向 `CommentsController` 控制器發起 `DELETE /articles/:article_id/comments/:id` 請求。我們可以從這個請求中找到要刪除的評論。下面在控制器中加入 `destroy` 動作(`app/controllers/comments_controller.rb`): ``` 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` 動作先查找當前文章,然后在 `@article.comments` 集合中找到對應的評論,將其從數據庫中刪掉,最后轉向顯示文章的頁面。 #### 8.1 刪除關聯對象 如果刪除一篇文章,也要刪除文章中的評論,不然這些評論會占用數據庫空間。在 Rails 中可以在關聯中指定 `dependent` 選項達到這一目的。把 `Article` 模型(`app/models/article.rb`)修改成: ``` class Article < ActiveRecord::Base has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } end ``` ### 9 安全 #### 9.1 基本認證 如果把這個博客程序放在網上,所有人都能添加、編輯、刪除文章和評論。 Rails 提供了一種簡單的 HTTP 身份認證機制可以避免出現這種情況。 在 `ArticlesController` 中,我們要用一種方法禁止未通過認證的用戶訪問其中幾個動作。我們需要的是 `http_basic_authenticate_with` 方法,通過這個方法的認證后才能訪問所請求的動作。 要使用這個身份認證機制,需要在 `ArticlesController` 控制器的頂部調用 `http_basic_authenticate_with` 方法。除了 `index` 和 `show` 動作,訪問其他動作都要通過認證,所以在 `app/controllers/articles_controller.rb` 中,要這么做: ``` class ArticlesController < ApplicationController http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show] def index @articles = Article.all end # snipped for brevity ``` 同時,我們還希望只有通過認證的用戶才能刪除評論。修改 `CommentsController` 控制器(`app/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 # snipped for brevity ``` 現在,如果想新建文章,會看到一個 HTTP 基本認證對話框。 ![HTTP 基本認證對話框](https://box.kancloud.cn/2016-05-11_5732ab0e6a346.png) 其他的身份認證方法也可以在 Rails 程序中使用。其中兩個比較流行的是 [Devise](https://github.com/plataformatec/devise) 引擎和 [Authlogic](https://github.com/binarylogic/authlogic) gem。 #### 9.2 其他安全注意事項 安全,尤其是在網頁程序中,是個很寬泛和值得深入研究的領域。Rails 程序的安全措施,在“[Ruby on Rails 安全指南](/security.html)”中有更深入的說明。 ### 10 接下來做什么 至此,我們開發了第一個 Rails 程序,請盡情地修改、試驗。在開發過程中難免會需要幫助,如果使用 Rails 時需要協助,可以使用這些資源: * [Ruby on Rails 指南](http://guides.ruby-china.org/index.html) * [Ruby on Rails 教程](http://railstutorial-china.org) * [Ruby on Rails 郵件列表](http://groups.google.com/group/rubyonrails-talk) * irc.freenode.net 上的 [#rubyonrails](irc://irc.freenode.net/#rubyonrails) 頻道 Rails 本身也提供了幫助文檔,可以使用下面的 rake 任務生成: * 運行 `rake doc:guides`,會在程序的 `doc/guides` 文件夾中生成一份 Rails 指南。在瀏覽器中打開 `doc/guides/index.html` 可以查看這份指南。 * 運行 `rake doc:rails`,會在程序的 `doc/api` 文件夾中生成一份完整的 API 文檔。在瀏覽器中打開 `doc/api/index.html` 可以查看 API 文檔。 使用 `doc:guides` 任務在本地生成 Rails 指南,要安裝 RedCloth gem。在 `Gemfile` 中加入這個 gem,然后執行 `bundle install` 命令即可。 ### 11 常見問題 使用 Rails 時,最好使用 UTF-8 編碼存儲所有外部數據。如果沒使用 UTF-8 編碼,Ruby 的代碼庫和 Rails 一般都能將其轉換成 UTF-8,但不一定總能成功,所以最好還是確保所有的外部數據都使用 UTF-8 編碼。 如果編碼出錯,常見的征兆是瀏覽器中顯示很多黑色方塊和問號。還有一種常見的符號是“??”,包含在“ü”中。Rails 內部采用很多方法盡量避免出現這種問題。如果你使用的外部數據編碼不是 UTF-8,有時會出現這些問題,Rails 無法自動糾正。 非 UTF-8 編碼的數據經常來源于: * 你的文本編輯器:大多數文本編輯器(例如 TextMate)默認使用 UTF-8 編碼保存文件。如果你的編輯器沒使用 UTF-8 編碼,有可能是你在模板中輸入了特殊字符(例如 é),在瀏覽器中顯示為方塊和問號。這種問題也會出現在國際化文件中。默認不使用 UTF-8 保存文件的編輯器(例如 Dreamweaver 的某些版本)都會提供一種方法,把默認編碼設為 UTF-8。記得要修改。 * 你的數據庫:默認情況下,Rails 會把從數據庫中取出的數據轉換成 UTF-8 格式。如果數據庫內部不使用 UTF-8 編碼,就無法保存用戶輸入的所有字符。例如,數據庫內部使用 Latin-1 編碼,用戶輸入俄語、希伯來語或日語字符時,存進數據庫時就會永遠丟失。如果可能,在數據庫中盡量使用 UTF-8 編碼。 ### 反饋 歡迎幫忙改善指南質量。 如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。 翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。 文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。 最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看