v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

Rails 入門

本指南介紹如何在 Rails 上啟動和執行 Ruby。

閱讀本指南後,您將瞭解:

1 指導假設

本指南專為想要開始建立 Rails 的初學者而設計 從頭開始申請。它不假設您有任何先前的經驗 與 Rails。

Rails 是一個執行在 Ruby 程式語言上的 Web 應用程式框架。 如果您之前沒有使用 Ruby 的經驗,您會發現學習非常陡峭 曲線直接潛入 Rails。有幾個精選的線上資源列表 學習Ruby:

請注意,一些資源雖然仍然很好,但包含了舊版本的 Ruby,並且可能不包含您將在日常中看到的某些語法 使用 Rails 進行開發。

2 什麼是Rails?

Rails是用Ruby程式語言編寫的Web應用開發框架。 它的目標在於通過做出假設來簡化 Web 應用程式的程式設計 關於每個開發人員需要什麼才能開始。它可以讓你寫得更少 程式碼,同時完成比許多其他語言和框架更多的工作。 有經驗的 Rails 開發者也報告說它使 Web 應用程式 開發更有趣。

Rails 是自以為是的軟體。它假設有一個“最好的” 做事的方式,它的目標在於鼓勵這種方式 - 在某些情況下 勸阻替代品。如果您學習了“Rails 方式”,您可能會發現 生產力的巨大提高。如果你堅持把舊習慣從 其他語言到您的 Rails 開發,並嘗試使用您的模式 在別處學到的,你可能會有不那麼愉快的經歷。

Rails 哲學包括兩大指導原則:

  • 不要重複自己: DRY 是軟體開發的一個原則 指出“每一條知識都必須有一個單一的、明確的、權威的 系統內的表示”。通過不一遍又一遍地寫入相同的資訊 同樣,我們的程式碼更易於維護、更可擴充套件且錯誤更少。
  • Convention Over Configuration: Rails 對做很多事情的最佳方式有意見 Web 應用程式中的事物,並且預設為這組約定,而不是 要求您通過無盡的設定檔案指定細節。

3 建立一個新的 Rails 專案

閱讀本指南的最佳方式是循序漸進。所有步驟都是 執行此示例應用程式必不可少,無需額外的程式碼或步驟 需要。

按照本指南進行操作,您將建立一個名為 Rails 的專案 blog,一個(非常)簡單的部落格。在開始構建應用程式之前, 你需要確保你已經安裝了 Rails 本身。

下面的例子使用 $ 來代表你在類 UNIX 作業系統中的終端提示, 儘管它可能已被定製為不同的外觀。如果您使用的是 Windows, 您的提示將類似於 C:\source_code>

3.1 安裝 Rails

在安裝 Rails 之前,您應該檢查以確保您的系統具有 安裝了適當的先決條件。這些包括:

  • Ruby
  • SQLite3
  • Node.js
  • Yarn
3.1.1 安裝 Ruby

開啟命令列提示符。在 macOS 上開啟 Terminal.app;在 Windows 上選擇 從開始選單中“執行”並輸入 cmd.exe。任何以 a 開頭的命令 美元符號 $ 應該在命令列中執行。驗證您是否擁有 當前版本的 Ruby 已安裝:

$ ruby --version
ruby 2.7.0

Rails 需要 Ruby 2.7.0 或更高版本。最好使用最新的 Ruby 版本。 如果返回的版本號小於該數字(例如 2.3.7 或 1.8.7), 您需要安裝 Ruby 的新副本。

要在 Windows 上安裝 Rails,您首先需要安裝 Ruby 安裝程式

有關大多數作業系統的更多安裝方法,請檢視 ruby-lang.org

3.1.2 安裝 SQLite3

您還需要安裝 SQLite3 資料庫。 許多流行的類 UNIX 作業系統都附帶了可接受的 SQLite3 版本。 其他人可以在 SQLite3 網站 找到安裝說明。

驗證它是否已正確安裝並在您的負載 PATH 中:

$ sqlite3 --version

程式應該報告它的版本。

3.1.3 安裝 Node.js 和 Yarn

最後,您需要安裝 Node.js 和 Yarn 來管理應用程式的 JavaScript。

Node.js 網站 上找到安裝說明和 使用以下命令驗證它是否正確安裝:

$ node --version

你的 Node.js 執行時的版本應該被打印出來。確保它更大 比 8.16.0。

安裝Yarn,按照安裝 Yarn 網站 上的說明。

執行此命令應打印出 Yarn 版本:

$ yarn --version

如果顯示“1.22.0”之類的內容,則說明 Yarn 已正確安裝。

3.1.4 安裝 Rails

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

$ gem install rails

要驗證您是否正確安裝了所有內容,您應該能夠 在新終端中執行以下命令:

$ rails --version

如果它顯示類似“Rails 7.0.0”的內容,您就可以繼續了。

3.2 建立部落格應用程式

Rails 附帶了許多稱為產生器的指令碼,這些指令碼的目標在於使 通過建立開始所需的一切,您的開發生活更輕鬆 從事特定的任務。其中之一是新的應用程式產生器, 這將為您提供一個全新的 Rails 應用程式的基礎,以便 你不必自己寫。

要使用此產生器,請開啟終端,導航到您擁有的目錄 建立檔案和執行的許可權:

$ rails new blog

這將在 blog 目錄中建立一個名為 Blog 的 Rails 應用程式和 使用安裝 Gemfile 中已經提到的 gem 依賴項 bundle install

提示:您可以看到 Rails 應用程式的所有命令列選項 產生器通過執行 rails new --help 來接受。

建立部落格應用程式後,切換到其資料夾:

$ cd blog

blog 目錄將有許多產生的檔案和資料夾,它們使 向上 Rails 應用程式的結構。本教程中的大部分工作將 發生在 app 資料夾中,但這裡有每個功能的基本概述 Rails 預設建立的檔案和資料夾:

檔案/資料夾 目的
app/ 包含應用程式的 controllers、models、views、helpers、郵件程式、渠道、作業和資產。在本指南的其餘部分,您將重點關注此資料夾。
bin/ 包含用於啟動應用程式的 rails 指令碼,並且可以包含用於設定、更新、部署或執行​​應用程式的其他指令碼。
config/ 包含應用程式路由、資料庫等的設定。這在 設定 Rails 應用程式 中有更詳細的介紹。
config.ru Rack 用於啟動應用程式的根據 Rack 的伺服器的設定。關於Rack的更多資訊,請參見Rack網站
db/ 包含您當前的資料庫模式,以及資料庫 migrations。
Gemfile
Gemfile.lock
這些檔案允許您指定 Rails 應用程式需要哪些 gem 依賴項。這些檔案由 Bundler gem 使用。有關 Bundler 的更多資訊,請參閱 Bundler 網站
lib/ 為您的應用程式擴充套件了 modules。
log/ 應用程式日誌檔案。
public/ 包含靜態檔案和編譯資產。當您的應用程式執行時,此目錄將按原樣公開。
Rakefile 該檔案定位並載入可以從命令列執行的任務。任務定義在 Rails 的整個元件中都有定義。您應該通過將檔案新增到應用程式的 lib/tasks 目錄來新增您自己的任務,而不是更改 Rakefile
README.md 這是您的應用程式的簡要說明手冊。您應該編輯此檔案以告訴其他人您的應用程式是做什麼的,如何設定它,等等。
storage/ 用於磁碟服務的Active Storage 檔案。這在 Active Storage Overview 中有介紹。
test/ 單元測試、夾具和其他測試裝置。這些在測試Rails應用程式中有介紹。
tmp/ 臨時檔案(如快取和 pid 檔案)。
vendor/ 所有第三方程式碼的地方。在典型的 Rails 應用程式中,這包括供應商 gems。
.gitignore 這個檔案告訴 git 它應該忽略哪些檔案(或模式)。有關忽略檔案的更多資訊,請參閱 GitHub - 忽略檔案
.ruby-version 此檔案包含預設的 Ruby 版本。

4 你好,Rails!

首先,讓我們快速在螢幕上顯示一些文字。為此,您需要 讓您的 Rails 應用程式伺服器執行。

4.1 啟動 Web 伺服器

您實際上已經有一個功能正常的 Rails 應用程式。要看到它,你需要 在您的開發機器上啟動一個 Web 伺服器。您可以通過執行 blog 目錄中的以下命令:

$ bin/rails server

提示:如果您使用的是 Windows,則必須在 bin 下傳遞指令碼 資料夾直接到 Ruby 直譯器,例如ruby bin\rails server

提示:JavaScript 資產壓縮需要您 在你的系統上有一個可用的 JavaScript 執行時,如果沒有 在執行時,您將在資產壓縮期間看到 execjs 錯誤。 通常 macOS 和 Windows 都安裝了 JavaScript 執行時。 therubyrhino 是 JRuby 使用者的推薦執行時,由 在 JRuby 下產生的應用程式中預設為 Gemfile。你可以調查 ExecJS 支援的所有執行時。

這將啟動 Puma,一個預設與 Rails 一起分發的 Web 伺服器。檢視 您在 action 中的應用程式,開啟瀏覽器視窗並導航到 http://localhost:3000。您應該會看到 Rails 預設資訊頁面:

![耶!你在Rails!螢幕截圖](圖片/getting_started/rails_welcome.png)

當您想停止 Web 伺服器時,請在終端視窗中按 Ctrl+C 它正在執行。在開發環境中,Rails一般不會 要求您重新啟動伺服器;您在檔案中所做的更改將是 由伺服器自動拾取。

“耶!你在 Rails!”頁面是新 Rails 的 smoke test 應用程式:它確保您的軟體設定正確 足以提供一個頁面。

4.2 說“你好”,Rails

要讓 Rails 說“你好”,你至少需要建立一個 route,一個 controller 帶有一個 action 和一個 view。路由將請求對映到 controller action。 A controller action 執行必要的工作來處理 請求,併為 view 準備任何資料。 A view 以所需的方式顯示資料 格式。

在實現方面:路由是用 Ruby DSL (特定領域語言)。 Controller 是 Ruby 類,它們的公共方法是 actions。還有views 是模板,通常用 HTML 和 Ruby 混合編寫。

讓我們首先在我們的路由檔案 config/routes.rb 中新增一個路由 Rails.application.routes.draw 塊的頂部:

Rails.application.routes.draw do
  get "/articles", to: "articles#index"

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

上面的路由聲明瞭 GET /articles 請求被對映到了 index action 中的 ArticlesController

要建立 ArticlesController 及其 index action,我們將執行 controller 產生器(使用 --skip-routes 選項,因為我們已經有了一個 合適的路線):

$ bin/rails generate controller Articles index --skip-routes

Rails 會為你建立幾個檔案:

create  app/controllers/articles_controller.rb
invoke  erb
create    app/views/articles
create    app/views/articles/index.html.erb
invoke  test_unit
create    test/controllers/articles_controller_test.rb
invoke  helper
create    app/helpers/articles_helper.rb
invoke    test_unit
invoke  assets
invoke    scss
create      app/assets/stylesheets/articles.scss

其中最重要的是 controller 檔案, app/controllers/articles_controller.rb。讓我們來看看它:

class ArticlesController < ApplicationController
  def index
  end
end

index action 為空。當 action 未顯式呈現 view 時 (或以其他方式觸發 HTTP 回應),Rails 將自動呈現 view 匹配 controller 和 action 的名稱。公約結束 設定! View 位於 app/views 目錄中。所以index action 預設會渲染 app/views/articles/index.html.erb

讓我們開啟 app/views/articles/index.html.erb,並將其內容替換為:

<h1>Hello, Rails!</h1>

如果您之前停止了 Web 伺服器以執行 controller 產生器, 用 bin/rails server 重新啟動它。現在訪問 http://localhost:3000/articles, 並看到我們顯示的文字!

4.3 設定應用首頁

目前,http://localhost:3000 仍然顯示“Yay!You're on Rails!”。 讓我們顯示我們的“你好,Rails!” http://localhost:3000 的文字也是如此。去做 所以,我們將新增一個路由,將我們的應用程式的根路徑對映到 適當的 controller 和 action。

我們開啟config/routes.rb,在頂部新增如下root路由 Rails.application.routes.draw 塊:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
end

現在我們可以看到我們的“你好,Rails!”當我們訪問 http://localhost:3000 時的文字, 確認 root 路由也對映到 index action ArticlesController

提示:要了解有關路由的更多資訊,請參閱 [Rails 從外向內路由]( 路由.html)。

5 MVC 和你

到目前為止,我們已經討論了路由,controllers、actions 和 views。所有這些 是遵循 MVC (Model-View-Controller) 模式。 MVC 是一種設計模式,它將應用程式的職責劃分為 使其更容易推理。 Rails 按照慣例遵循此設計模式。

由於我們有一個 controller 和一個 view 可以使用,讓我們產生下一個 件:model。

5.1 產生 Model

model 是一個 Ruby 類,用於表示資料。此外,models 可以通過一個名為 Rails 的特性與應用程式的資料庫進行互動 Active Record

要定義 model,我們將使用 model 產生器:

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

Model 名稱是單數,因為實例化的模型代表 單個數據記錄。為了幫助記住這個約定,想想你會如何 呼叫model的建構函式:我們要寫Article.new(...)不是 Articles.new(...)

這將建立幾個檔案:

invoke  active_record
create    db/migrate/<timestamp>_create_articles.rb
create    app/models/article.rb
invoke    test_unit
create      test/models/article_test.rb
create      test/fixtures/articles.yml

我們將關注的兩個檔案是 migration 檔案 (db/migrate/<timestamp>_create_articles.rb) 和 model 檔案 (app/models/article.rb)。

5.2 資料庫 Migrations

Migrations 用於更改應用程式資料庫的結構。在 Rails 應用程式,migrations 寫在 Ruby 中,以便它們可以 與資料庫無關。

讓我們看看我們新建的 migration 檔案的內容:

class CreateArticles < ActiveRecord::Migration[7.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

create_table 的呼叫指定了 articles 表應該如何 建造。預設情況下,create_table 方法新增一個 id 列作為 自動遞增主 key。所以表中的第一條記錄會有一個 id 為 1,下一條記錄的 id 將為 2,依此類推。

create_table 的塊內,定義了兩列:titlebody。這些是由產生器新增的,因為我們將它們包含在我們的 產生命令 (bin/rails generate model Article title:string body:text)。

該塊的最後一行是對 t.timestamps 的呼叫。該方法定義 兩個名為 created_atupdated_at 的附加列。正如我們將看到的, Rails 會為我們管理這些,當我們建立或更新一個時設定 values model 物件。

讓我們使用以下命令執行我們的 migration:

$ bin/rails db:migrate

該命令將顯示指示表已建立的輸出:

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

提示:要了解有關遷移的更多資訊,請參閱 [Active Record Migrations]( active_record_migrations.html)。

現在我們可以使用 model 與表進行互動。

5.3 使用 Model 與資料庫互動

為了稍微玩一下我們的 model,我們將使用 Rails 的一個特性,稱為 安慰。控制檯是一個和 irb 一樣的互動式編碼環境,但是 它還會自動載入 Rails 和我們的應用程式程式碼。

讓我們用這個命令啟動控制檯:

$ bin/rails console

您應該會看到一個 irb 提示,例如:

Loading development environment (Rails 7.0.0)
irb(main):001:0>

在這個提示下,我們可以初始化一個新的 Article 物件:

irb> article = Article.new(title: "Hello Rails", body: "I am on Rails!")

重要的是要注意我們只初始化了這個物件。這個物件 根本沒有儲存到資料庫中。它僅在控制檯中可用 片刻。要將物件儲存到資料庫,我們必須呼叫 [save]( https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save):

irb> article.save
(0.1ms)  begin transaction
Article Create (0.4ms)  INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms)  commit transaction
=> true

上面的輸出顯示了一個 INSERT INTO "articles" ... 資料庫查詢。這 表示文章已插入到我們的表中。如果我們採取 再次檢視 article 物件,我們看到發生了一些有趣的事情:

irb> article
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

現在設定了物件的 idcreated_atupdated_at 屬性。 當我們儲存物件時,Rails 為我們做了這個。

當我們想從資料庫中獲取這篇文章時,我們可以呼叫find 在 model 上並將 id 作為引數傳遞:

irb> Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

當我們想從資料庫中獲取所有文章時,我們可以呼叫 all 在 model 上:

irb> Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>

此方法返回 ActiveRecord::Relation 物件,其中 您可以將其視為超強陣列。

提示:要了解有關 models 的更多資訊,請參閱 Active Record 基礎知識 和 [Active Record 查詢介面]( active_record_querying.html)。

Models 是 MVC 難題的最後一塊。接下來,我們將連線所有 拼湊在一起。

5.4 顯示文章列表

讓我們回到我們在 app/controllers/articles_controller.rb 中的 controller,然後 更改 index action 以從資料庫中獲取所有文章:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

Controller 實例變數可以被 view 訪問。這意味著我們可以 在 app/views/articles/index.html.erb 中引用 @articles。讓我們開啟那個 檔案,並將其內容替換為:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= article.title %>
    </li>
  <% end %>
</ul>

上面的程式碼是 HTML 和 ERB 的混合體。 ERB 是一個模板系統 評估嵌入在文件中的 Ruby 程式碼。在這裡,我們可以看到兩種型別的 ERB 標籤:<% %><%= %><% %> 標籤的意思是“評估封閉的 Ruby 程式碼。” <%= %> 標籤的意思是“評估封閉的 Ruby 程式碼,並輸出 value 它返回。”你可以在正常 Ruby 程式中編寫的任何內容都可以 在這些 ERB 標籤內,雖然通常最好保留 ERB 標籤的內容 簡而言之,為了可讀性。

由於我們不想輸出由 @articles.each 返回的 value,我們已經 將該程式碼包含在 <% %> 中。但是,因為我們確實想要輸出 value 由 article.title 返回(對於每篇文章),我們已將該程式碼包含在 <%= %>

我們可以通過訪問 http://localhost:3000 來檢視最終結果。 (記住 bin/rails server 必須正在執行!)當我們這樣做時會發生以下情況:

1.瀏覽器發出請求:GET http://localhost:3000。 2. 我們的 Rails 應用程式接收到這個請求。 3. Rails路由器將根路由對映到ArticlesControllerindex action。 4. index action 使用Article model 來獲取資料庫中的所有文章。 5. Rails 自動渲染 app/views/articles/index.html.erb view。 6. view 中的ERB 程式碼被評估以輸出HTML。 7. 伺服器將包含 HTML 的回應傳送回瀏覽器。

我們已經將所有 MVC 部分連線在一起,我們有了我們的第一個 controller action!接下來,我們將轉到第二個 action。

6 CRUDit CRUDit 到期的地方

幾乎所有的 Web 應用程式都涉及 CRUD(建立、讀取、更新和刪除) 操作。你 甚至可能會發現您的應用程式所做的大部分工作都是 CRUD。 Rails 承認這一點,並提供許多功能來幫助簡化執行 CRUD 的程式碼。

讓我們通過新增更多功能開始探索這些功能 應用。

6.1 顯示單篇文章

我們目前有一個 view 列出了我們資料庫中的所有文章。讓我們新增一個 新的 view 顯示單個文章的標題和正文。

我們首先新增一條對映到新 controller action(我們 接下來會補充)。開啟 config/routes.rb,並插入此處顯示的最後一條路線:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
  get "/articles/:id", to: "articles#show"
end

新路由是另一個 get 路由,但它的路徑中有一些額外的東西: :id。這指定了一個路由引數。路由引數捕獲一個段 請求的路徑,並將該 value 放入 params 雜湊中,即 可通過 controller action 訪問。例如,在處理像這樣的請求時 GET http://localhost:3000/articles/11 將被捕獲為 value :id,然後可以作為 show action 中的 params[:id] 訪問 ArticlesController

現在讓我們在 index action 下方新增 show action app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end
end

show action 呼叫 Article.find提到 以前) 與捕獲的 ID 通過路由引數。返回的文章存放在@article 實例變數,所以它可以被 view 訪問。預設情況下,show action 將呈現 app/views/articles/show.html.erb

讓我們建立 app/views/articles/show.html.erb,內容如下:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

現在我們訪問http://localhost:3000/articles/1就可以看到文章了!

最後,讓我們新增一個方便的方法來訪問文章頁面。我們將連結 app/views/articles/index.html.erb 中每篇文章的標題到其頁面:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="/articles/<%= article.id %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

6.2 資源豐富的路由

到目前為止,我們已經介紹了 CRUD 的“R”(讀取)。我們最終將包含“C” (建立)、“U”(更新)和“D”(刪除)。正如您可能已經猜到的那樣,我們會做 所以通過新增新的路由,controller actions 和 views。每當我們有這樣的 路由的組合,controller actions 和 views 一起工作 對實體執行 CRUD 操作,我們稱該實體為 資源。為了 例如,在我們的應用程式中,我們會說一篇文章是一種資源。

Rails 提供了一個名為 resources 對映資源集合的所有正常路線,例如 文章。因此,在我們繼續“C”、“U”和“D”部分之前,讓我們替換 config/routes.rbresources 中的兩條 get 路由:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

我們可以通過執行 bin/rails routes 命令來檢查映射了哪些路由:

$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
        root GET    /                            articles#index
    articles GET    /articles(.:format)          articles#index
 new_article GET    /articles/new(.:format)      articles#new
     article GET    /articles/:id(.:format)      articles#show
             POST   /articles(.:format)          articles#create
edit_article GET    /articles/:id/edit(.:format) articles#edit
             PATCH  /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy

resources 方法還設定了我們可以使用的 URL 和路徑 helper 方法 使我們的程式碼不依賴於特定的路由設定。 values 在上面的“字首”列中加上字尾 _url_path 形成名稱 其中 helpers。例如,article_path 助手返回 "/articles/#{article.id}" 當給定一篇文章時。我們可以用它來整理我們的 app/views/articles/index.html.erb 中的連結:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="<%= article_path(article) %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

但是,我們將通過使用 link_to helper。 link_to helper 呈現一個連結,其第一個引數作為 連結的文字及其作為連結目的地的第二個引數。如果我們通過一個 model 物件作為第二個引數,link_to 會呼叫相應的路徑 helper 將物件轉換為路徑。例如,如果我們傳遞一篇文章, link_to 將呼叫 article_path。所以app/views/articles/index.html.erb 變成:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

好的!

提示:要了解有關路由的更多資訊,請參閱 [Rails 從外向內路由]( 路由.html)。

6.3 建立新文章

現在我們轉到 CRUD 的“C”(建立)。通常,在 Web 應用程式中, 建立新資源是一個多步驟的過程。首先,使用者請求一個表單 填寫。然後,使用者提交表單。如果沒有錯誤,那麼 資源已建立並顯示某種確認。否則,表格 重新顯示錯誤訊息,並重復該過程。

在 Rails 應用程式中,這些步驟通常由 controller 的 newcreate actions。讓我們新增這些 actions 的典型實現 到 app/controllers/articles_controller.rb,低於 show action:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(title: "...", body: "...")

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
end

new action 實例化一個新文章,但不儲存它。本文 將在構建表單時在 view 中使用。預設情況下,new action 將渲染 app/views/articles/new.html.erb,我們接下來將建立它。

create action 實例化一篇新文章,標題為 values 身體,並試圖挽救它。如果文章儲存成功,則action 將瀏覽器重定向到位於 "http://localhost:3000/articles/#{@article.id}" 的文章頁面。 否則,action 通過渲染 app/views/articles/new.html.erb 重新顯示錶單 使用狀態程式碼 4XX 使應用程式與 Turbo 一起正常工作。 這裡的標題和正文是虛擬的 values。在我們建立好表格之後,我們會來 返回並更改這些。

redirect_to 將導致瀏覽器發出新請求, 而 render 為當前請求呈現指定的 view。 在改變資料庫或應用程式狀態後使用 redirect_to 很重要。 否則,如果使用者重新整理頁面,瀏覽器會發出相同的請求,並且會重複修改。

6.3.1 使用表單產生器

我們將使用 Rails 的一個稱為 表單構建器 的功能來建立我們的表單。使用 一個表單構建器,我們可以編寫最少的程式碼來輸出一個表單 完全設定並遵循 Rails 約定。

讓我們使用以下內容建立 app/views/articles/new.html.erb

<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

form_with helper 方法實例化一個表單構建器。在 form_with 塊中我們呼叫 像 label 這樣的方法 和 text_field 在表單構建器上輸出適當的表單元素。

我們的 form_with 呼叫的結果輸出將如下所示:

<form action="/articles" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="...">

  <div>
    <label for="article_title">Title</label><br>
    <input type="text" name="article[title]" id="article_title">
  </div>

  <div>
    <label for="article_body">Body</label><br>
    <textarea name="article[body]" id="article_body"></textarea>
  </div>

  <div>
    <input type="submit" name="commit" value="Create Article" data-disable-with="Create Article">
  </div>
</form>

提示:要了解有關表單構建器的更多資訊,請參閱 [Action View 表單 Helpers]( form_helpers.html)。

6.3.2 使用強引數

提交的表單資料被放入 params Hash,與捕獲的路由一起 引數。因此,create action 可以通過以下方式訪問提交的標題 params[:article][:title] 和通過 params[:article][:body] 提交的正文。 我們可以將這些 values 單獨傳遞給 Article.new,但這將是 冗長且可能容易出錯。隨著我們新增更多,情況會變得更糟 領域。

相反,我們將傳遞一個包含 values 的單個雜湊。然而,我們必須 仍然指定該雜湊中允許使用的 values。否則,惡意使用者 可能會提交額外的表單欄位並覆蓋私人資料。實際上, 如果我們將未經過濾的 params[:article] 雜湊直接傳遞給 Article.new, Rails 會引發一個 ForbiddenAttributesError 來提醒我們這個問題。 因此,我們將使用 Rails 的一個稱為 強引數 的特性來過濾 params。 將其視為 強型別 對於 params

讓我們在 app/controllers/articles_controller.rb 底部新增一個私有方法 名為 article_params 過濾 params。讓我們改變 create 來使用 它:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

提示:要了解有關強引數的更多資訊,請參閱 Action Controller Overview § 強引數

6.3.3 驗證和顯示錯誤訊息

正如我們所見,建立資源是一個多步驟的過程。處理無效 使用者輸入是該過程的另一個步驟。 Rails 提供了一個名為 validations 幫助我們處理無效的使用者輸入。驗證是規則 在儲存 model 物件之前檢查。如果任何檢查失敗, 儲存將被中止,並且適當的錯誤訊息將被新增到 model 物件的 errors 屬性。

讓我們在 app/models/article.rb 中為我們的 model 新增一些驗證:

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

第一個驗證宣告 title value 必須存在。因為 title 是一個字串,這意味著 title value 必須至少包含一個 非空白字元。

第二個驗證宣告 body value 也必須存在。 此外,它還宣告 body value 必須至少為 10 個字元 長。

您可能想知道 titlebody 屬性是在哪裡定義的。 Active Record 自動為每個表列定義了 model 屬性,所以 您不必在 model 檔案中宣告這些屬性。

驗證到位後,讓我們將 app/views/articles/new.html.erb 修改為 顯示 titlebody 的任何錯誤訊息:

<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% @article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% @article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

full_messages_for 方法返回指定的使用者友好的錯誤訊息陣列 屬性。如果該屬性沒有錯誤,則陣列將為空。

要了解所有這些是如何協同工作的,讓我們再看一下 newcreate controller actions:

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

當我們訪問 http://localhost:3000/articles/new 時,GET /articles/new 請求對映到 new action。 new action 不嘗試儲存 @article。因此,不檢查驗證,不會有錯誤 訊息。

當我們提交表單時,將 POST /articles 請求對映到 create action。 create action 確實嘗試儲存 @article。所以, 驗證檢查。如果任何驗證失敗,則 @article 將不會被 儲存,app/views/articles/new.html.erb 會報錯 帶有狀態程式碼 4XX 的訊息,以便應用程式與 Turbo 一起正常工作。

提示:要了解有關驗證的更多資訊,請參閱 [Active Record 驗證]( active_record_validations.html)。要了解有關驗證錯誤訊息的更多資訊, 請參閱 [Active Record 驗證 § 處理驗證錯誤]( active_record_validations.html#working-with-validation-errors)。

6.3.4 收尾

我們現在可以通過訪問 http://localhost:3000/articles/new 建立一篇文章。 最後,讓我們從底部連結到該頁面 app/views/articles/index.html.erb

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

6.4 更新文章

我們已經介紹了 CRUD 的“CR”。現在讓我們繼續討論“U”(更新)。更新中 資源與建立資源非常相似。它們都是多步驟的 過程。首先,使用者請求一個表單來編輯資料。然後,使用者 提交表單。如果沒有錯誤,則更新資源。別的, 表單將重新顯示錯誤訊息,並重復該過程。

這些步驟通常由 controller 的 editupdate 處理 actions。讓我們將這些 actions 的典型實現新增到 app/controllers/articles_controller.rb,低於 create action:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

注意 editupdate actions 與 newcreate 的相似之處 actions。

edit action 從資料庫中獲取文章,並將其儲存在 @article 以便在構建表單時可以使用它。預設情況下,edit action 將呈現 app/views/articles/edit.html.erb

update action(重新)從資料庫中獲取文章,並嘗試 使用由 article_params 過濾的提交表單資料更新它。如果不 驗證失敗,更新成功,action 重定向瀏覽器 到文章頁面。否則,action 會重新顯示錶單並出現錯誤 訊息,通過使用狀態程式碼 4XX 呈現 app/views/articles/edit.html.erb 使應用程式與 Turbo 一起正常工作。

6.4.1 使用 Partials 共享 View 程式碼

我們的 edit 表單看起來與我們的 new 表單相同。甚至程式碼將是 同樣,感謝 Rails 表單構建器和足智多謀的路由。表單產生器 自動設定表單以發出適當型別的請求,根據 關於 model 物件之前是否已儲存。

因為程式碼是相同的,我們將把它分解成一個共享的 view 稱為部分。讓我們建立 app/views/articles/_form.html.erb 以下內容:

<%= form_with model: article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

上面的程式碼和我們在app/views/articles/new.html.erb中的表格一樣, 除了所有出現的 @article 都被 article 替換。 由於部分是共享程式碼,因此最好不要依賴它們 由 controller action 設定的特定實例變數。相反,我們將通過 文章以區域性變數作為區域性變數。

讓我們更新 app/views/articles/new.html.erb 以通過 [render]( https://api.rubyonrails.org/classes/ActionView/Helpers/RenderingHelper.html#method-i-render):

<h1>New Article</h1>

<%= render "form", article: @article %>

部分的檔名必須以 ** 和** 下劃線為字首,例如 _form.html.erb。但是在渲染時,它被引用沒有 下劃線,例如render "form"

現在,讓我們建立一個非常相似的 app/views/articles/edit.html.erb

<h1>Edit Article</h1>

<%= render "form", article: @article %>

提示:要了解有關部分的更多資訊,請參閱 Rails 中的佈局和渲染§使用 部分

6.4.2 收尾

我們現在可以通過訪問其編輯頁面來更新文章,例如 http://localhost:3000/articles/1/edit。最後,讓我們連結到編輯 app/views/articles/show.html.erb 底部的頁面:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
</ul>

6.5 刪除文章

最後,我們到達 CRUD 的“D”(刪除)。刪除資源更簡單 過程而不是建立或更新。它只需要一個路由和一個 controller action。我們足智多謀的路由 (resources :articles) 已經提供了 路由,將 DELETE /articles/:id 請求對映到 destroy action ArticlesController

所以,讓我們新增一個典型的 destroy action 到 app/controllers/articles_controller.rb, 在 update action 下方:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

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

    redirect_to root_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

destroy action 從資料庫中獲取文章,並呼叫 destroy 在上面。然後,它將瀏覽器重定向到根路徑。

我們選擇重定向到根路徑,因為這是我們的主要訪問 文章點。但是,在其他情況下,您可能會選擇重定向到 例如articles_path

現在讓我們在 app/views/articles/show.html.erb 底部新增一個連結,以便 我們可以從它自己的頁面中刪除一篇文章:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

在上面的程式碼中,我們向 link_to 傳遞了一些額外的選項。這 method: :delete 選項導致連結發出 DELETE 請求而不是 GET 請求。 data: { confirm: "Are you sure?" } 選項會導致 單擊連結時出現的確認對話方塊。如果使用者取消 對話方塊,請求被中止。這兩個選項都由一個功能提供支援 Rails 稱為 Unobtrusive JavaScript (UJS)。 JavaScript 檔案 實現這些行為預設包含在新的 Rails 應用程式中。

提示:要了解有關 Unobtrusive JavaScript 的更多資訊,請參閱 Working With JavaScript in Rails

就是這樣!我們現在可以列出、顯示、建立、更新和刪除文章! 不可CRUDable!

7 新增第二個 Model

是時候嚮應用程式新增第二個 model 了。第二個 model 將處理 對文章的評論。

7.1 產生 Model

我們將看到與之前建立時使用的相同的產生器 Article model。這次我們將建立一個 Comment model 來儲存一個 參考一篇文章。在終端中執行此命令:

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

此命令將產生四個檔案:

檔案 目的
db/migrate/20140120201010_create_comments.rb Migration 在您的資料庫中建立評論表(您的姓名將包含不同的時間戳)
app/models/comment.rb 評論模型
測試/models/comment_test.rb 評論模型的測試工具
測試/夾具/comments.yml 用於測試的示例註釋

首先看一下app/models/comment.rb

class Comment < ApplicationRecord
  belongs_to :article
end

這與您之前看到的 Article model 非常相似。差別 是行 belongs_to :article,它設定了一個 Active Record association。 您將在本指南的下一部分中瞭解一些關於 associations 的知識。

bash 命令中使用的 (:references) key 字是 models 的特殊資料型別。 它會在您的資料庫表上建立一個新列,並使用提供的 model 名稱附加 _id 可以容納整數 values。為了更好地理解,分析 執行 migration 後的 db/schema.rb 檔案。

除了model,Rails還做了一個migration來建立 對應的資料庫表:

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

      t.timestamps
    end
  end
end

t.references 行建立了一個名為 article_id 的整數列,一個索引 對於它,以及指向 articlesid 列的外部 key 約束 桌子。繼續執行 migration:

$ bin/rails db:migrate

Rails 足夠聰明,只執行尚未執行的 migrations 針對當前資料庫執行,因此在這種情況下,您將只看到:

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

7.2 關聯 Model

Active Record associations讓你輕鬆宣告兩者的關係 models。在評論和文章的情況下,你可以寫出 這樣的關係:

  • 每條評論屬於一篇文章。
  • 一篇文章可以有很多評論。

其實這和Rails用來宣告這個的語法很接近 association。您已經看到了 Comment model 中的程式碼行 (app/models/comment.rb) 使每條評論都屬於一篇文章:

class Comment < ApplicationRecord
  belongs_to :article
end

您需要編輯 app/models/article.rb 以新增 association:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

這兩個宣告啟用了一些自動行為。例如,如果 你有一個包含文章的實例變數 @article,你可以檢索 屬於該文章的所有評論作為陣列使用 @article.comments

提示:有關 Active Record associations 的更多資訊,請參閱 Active Record Associations 指南。

7.3 新增評論路由

articles controller 一樣,我們需要新增一條路由,以便 Rails 知道我們想導航到哪裡檢視 comments。開啟 再次 config/routes.rb 檔案,編輯如下:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles do
    resources :comments
  end
end

這會在 articles 中建立 comments 作為巢狀資源。這是 捕獲之間存在的層次關係的另一部分 文章和評論。

提示:有關路由的更多資訊,請參閱 Rails 路由 指導。

7.4 產生 Controller

有了model,您就可以將注意力轉向建立匹配 controller。同樣,我們將使用之前使用的相同產生器:

$ bin/rails generate controller Comments

這將建立四個檔案和一個空目錄:

檔案/目錄 目的
app/controllers/comments_controller.rb 評論控制器
應用程式/檢視/評論/ controller 的 View 存放在這裡
測試/controllers/comments_controller_test.rb 控制器測試
app/helpers/comments_helper.rb view 幫助檔案
應用程式/資產/樣式表/comments.scss controller 的級聯樣式表

與任何部落格一樣,我們的讀者將在之後直接發表評論 閱讀文章,一旦他們添加了評論,就會被髮回 到文章顯示頁面檢視他們現在列出的評論。正因如此,我們的 CommentsController 是提供一種建立評論和刪除的方法 垃圾評論到達時。

首先,我們將連線文章顯示模板 (app/views/articles/show.html.erb) 讓我們做一個新的評論:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

這會在 Article 顯示頁面上新增一個表單,該表單通過以下方式建立新評論 呼叫 CommentsController create action。此處的 form_with 呼叫使用 一個數組,它將構建一個巢狀路由,例如 /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

你會在這裡看到比在 controller 中看到的複雜一點 文章。這是您設定的巢狀的副作用。每個請求 對於評論必須保留評論所附文章的 track, 因此初始呼叫 Article model 的 find 方法來獲取 有問題的文章。

此外,該程式碼利用了一些可用於 association。我們在 @article.comments 上使用 create 方法來建立和 儲存評論。這將自動連結評論,使其屬於 那篇特定的文章。

發表新評論後,我們會將使用者傳送回原始文章 使用 article_path(@article) helper。正如我們已經看到的,這呼叫 ArticlesControllershow action 依次呈現 show.html.erb 模板。這是我們想要顯示評論的地方,所以讓我們 將其新增到 app/views/articles/show.html.erb

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<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_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

現在您可以將文章和評論新增到您的部落格,並讓它們顯示在 正確的地方。

帶評論的文章

8 重構

現在我們有文章和評論工作,看看 app/views/articles/show.html.erb 模板。它變得漫長而尷尬。我們 可以使用partials來清理它。

8.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 看起來像 下列的:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

這現在將在 app/views/comments/_comment.html.erb 中渲染一次 對於 @article.comments 集合中的每條評論。作為 render 方法迭代 @article.comments 集合,它分配每個 對與部分名稱相同的區域性變數進行註釋,在這種情況下 comment,然後在partial中提供給我們展示。

8.2 渲染部分表單

讓我們也將新的評論部分移到它自己的部分。又是你 建立一個檔案 app/views/comments/_form.html.erb,其中包含:

<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

然後你讓 app/views/articles/show.html.erb 看起來像下面這樣:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render 'comments/form' %>

第二個渲染只是定義了我們要渲染的部分模板, comments/form。 Rails 足夠聰明,可以發現其中的正斜槓 字串並意識到您想在其中呈現 _form.html.erb 檔案 app/views/comments 目錄。

@article 物件可用於在 view 中呈現的任何部分,因為 我們將其定義為實例變數。

8.3 使用問題

關注點是一種使大型 controllers 或 models 更易於理解和管理的方法。當多個 models(或 controllers)共享相同的關注點時,這也具有可重用性的優勢。關注點是使用 modules 實現的,這些方法包含表示模型或控制器負責的定義明確的功能部分的方法。在其他語言中,modules 通常被稱為 mixin。

您可以在 controller 或 model 中使用關注點,就像使用任何 module 一樣。當您第一次使用 rails new blog 建立您的應用程式時,在 app/ 中建立了兩個資料夾以及其餘資料夾:

app/controllers/concerns
app/models/concerns

給定的部落格文章可能有多種狀態 - 例如,它可能對所有人可見(即 public),或僅對作者可見(即 private)。它也可能對所有人隱藏但仍然可以檢索(即 archived)。評論可以類似地隱藏或可見。這可以使用每個 model 中的 status 列來表示。

article model 中,在執行 migration 以新增 status 列後,您可以新增:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

並在 Comment model 中:

class Comment < ApplicationRecord
  belongs_to :article

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

然後,在我們的 index action 模板(app/views/articles/index.html.erb)中,我們將使用 archived? 方法來避免顯示任何已存檔的文章:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

同樣,在我們的評論部分 view (app/views/comments/_comment.html.erb) 中,我們將使用 archived? 方法來避免顯示任何已存檔的評論:

<% unless comment.archived? %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

但是,現在再看一下我們的models,就會發現邏輯是重複的。如果將來我們增加部落格的功能 - 例如,包括私人訊息 - 我們可能會發現自己再次複製邏輯。這就是擔憂派上用場的地方。

一個關注點只負責模型職責的一個集中子集;我們關注的方法都將與模型的可見性相關。讓我們稱我們的新關注點 (module) Visible。我們可以在 app/models/concerns 中建立一個名為 visible.rb 的新檔案,並存儲在 models 中複製的所有狀態方法。

app/models/concerns/visible.rb

module Visible
  def archived?
    status == 'archived'
  end
end

我們可以將狀態驗證新增到關注點中,但這稍微複雜一些,因為驗證是在類級別呼叫的方法。 ActiveSupport::ConcernAPI 指南)為我們提供了一種更簡單的方法來包含它們:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  def archived?
    status == 'archived'
  end
end

現在,我們可以從每個 model 中刪除重複的邏輯,而是包含我們新的 Visible module:

app/models/article.rb 中:

class Article < ApplicationRecord
  include Visible

  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

app/models/comment.rb 中:

class Comment < ApplicationRecord
  include Visible

  belongs_to :article
end

類方法也可以新增到關注點中。如果我們想在我們的主頁上顯示公共文章或評論的數量,我們可能會向 Visible 新增一個類方法,如下所示:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  class_methods do
    def public_count
      where(status: 'public').count
    end
  end

  def archived?
    status == 'archived'
  end
end

然後在 view 中,你可以像呼叫任何類方法一樣呼叫它:

<h1>Articles</h1>

Our blog has <%= Article.public_count %> articles and counting!

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

在我們的應用程式新增 status 列之前,還需要執行一些步驟。首先,讓我們執行以下 migrations 將 status 新增到 ArticlesComments

$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string

提示:要了解有關遷移的更多資訊,請參閱 [Active Record Migrations]( active_record_migrations.html)。

我們還必須允許 :status key 作為強引數的一部分,在 app/controllers/articles_controller.rb 中:

  private
    def article_params
      params.require(:article).permit(:title, :body, :status)
    end

app/controllers/comments_controller.rb 中:

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end

最後,我們將在表單中新增一個選擇框,讓使用者在建立新文章或發表新評論時選擇狀態。我們也可以指定預設狀態為 public。在 app/views/articles/_form.html.erb 中,我們可以新增:

<div>
  <%= form.label :status %><br>
  <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</div>

app/views/comments/_form.html.erb 中:

<p>
  <%= form.label :status %><br>
  <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</p>

9 刪除評論

部落格的另一個重要功能是能夠刪除垃圾評論。去做 為此,我們需要在 view 和 destroy 中實現某種連結 action 在 CommentsController 中。

首先,讓我們在 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>

點選這個新的“銷燬評論”連結將觸發DELETE /articles/:article_id/comments/:id to our CommentsController,然後可以 用這個找到我們要刪除的評論,所以我們新增一個destroy action 到我們的 controller (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, :status)
    end
end

destroy action 會找到我們正在看的文章,找到評論 在 @article.comments 集合中,然後將其從 資料庫並將我們傳送回該文章的節目 action。

9.1 刪除關聯物件

如果你刪除一篇文章,它的相關評論也需要被刪除 刪除,否則它們只會佔用資料庫中的空間。 Rails 允許 您可以使用 association 的 dependent 選項來實現這一點。修改 第model、app/models/article.rb,如下:

class Article < ApplicationRecord
  include Visible

  has_many :comments, dependent: :destroy

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

10 安全

10.1 基本認證

如果您要線上釋出您的部落格,任何人都可以新增、編輯和 刪除文章或刪除評論。

Rails 提供了一個 HTTP 身份驗證系統,可以很好地工作 這個情況。

ArticlesController 中,我們需要有一種方法來阻止訪問 各種 actions 如果此人未通過身份驗證。這裡我們可以使用 Rails http_basic_authenticate_with 方法,允許訪問請求的 action 如果該方法允許的話。

要使用身份驗證系統,我們在頂部指定它 ArticlesControllerapp/controllers/articles_controller.rb 中。在我們的例子中, 我們希望使用者在除 indexshow 之外的每個 action 上進行身份驗證, 所以我們這樣寫:

class ArticlesController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # snippet 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

  # snippet for brevity

現在,如果您嘗試建立一篇新文章,您將看到一個基本的 HTTP 身份驗證挑戰:

基本 HTTP 身份驗證挑戰

其他身份驗證方法可用於 Rails 應用程式。兩種流行 Rails 的身份驗證外掛是 設計 rails 引擎和 Authlogic 寶石, 與其他一些人一起。

10.2 其他安全注意事項

安全性,尤其是在 Web 應用程式中,是一個廣泛而詳細的領域。安全 在您的 Rails 應用程式中有更深入的介紹 Ruby 上的 Rails 安全指南

11 下一步是什麼?

現在您已經看到了您的第一個 Rails 應用程式,您應該可以隨意 更新它並自行試驗。

請記住,您不必在沒有幫助的情況下做所有事情。當您需要幫助時 使用 Rails 啟動並執行,請隨時諮詢這些支援 資源:

12 設定問題

使用 Rails 的最簡單方法是將所有外部資料儲存為 UTF-8。如果 你沒有,Ruby 庫和 Rails 通常能夠轉換你的原生 資料轉換為 UTF-8,但這並不總是可靠的,所以你最好 確保所有外部資料都是 UTF-8。

如果您在這方面犯了錯誤,最常見的症狀是黑色 瀏覽器中出現帶有問號的菱形。另一種常見的 症狀是出現像“ü”這樣的字元而不是“ü”。 Rails 取一個數 減輕這些問題的常見原因的內部步驟 自動檢測和糾正。但是,如果您有外部資料 不儲存為 UTF-8,它偶爾會導致這些型別的問題 無法被 Rails 自動檢測並糾正。

兩個非常常見的非 UTF-8 資料來源:

  • 你的文字編輯器:大多數文字編輯器(如TextMate),預設儲存 檔案為 UTF-8。如果您的文字編輯器沒有,這可能會導致特殊的 您在模板中輸入的字元(例如 é)以顯示為菱形 瀏覽器裡面有一個問號。這也適用於您的 i18n 翻譯檔案。大多數尚未預設為 UTF-8 的編輯器(例如 Dreamweaver 的某些版本)提供了一種將預設值更改為 UTF-8 的方法。做 所以。
  • 您的資料庫:Rails 預設將您資料庫中的資料轉換為 UTF-8 在邊界。但是,如果您的資料庫在內部沒有使用 UTF-8,它 可能無法儲存使用者輸入的所有字元。例如, 如果您的資料庫在內部使用 Latin-1,並且您的使用者輸入俄語, 希伯來文或日文字元,一旦進入,資料將永遠丟失 資料庫。如果可能,請使用 UTF-8 作為資料庫的內部儲存。

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區