1 Action Cable 是什麼?
Action Cable 無縫整合 WebSockets 了您的 rails 應用程式。 它允許在 Ruby 中寫入即時運算(real-time)功能與 Rails 應用程式的其餘部分相同的樣式和形式,同時仍然是效能和可擴充套件性。 這是一個全端產品,提供了客戶端 JavaScript 框架和伺服器端 Ruby 框架。 你有訪問使用 Active Record 或您的 ORM 編寫的完整域 model 選擇。
2 術語
Action Cable 使用 WebSockets 代替 HTTP 請求-回應協議。 Action Cable 和 WebSocket 都引入了一些不太熟悉的術語:
2.1 連線
連線構成了客戶端-伺服器關係的基礎。 單個 Action Cable 伺服器可以處理多個連線實例。它有一個 每個 WebSocket 連線的連線實例。一個使用者可能有多個 如果 WebSocket 使用多個瀏覽器頁籤或裝置,則它們會向您的應用程式開啟。
2.2 消費者
WebSocket 連線的客戶端稱為消費者。在 Action Cable 消費者由客戶端 JavaScript 框架建立。
2.3 頻道
每個消費者可以依次訂閱多個頻道。每個通道
封裝了一個邏輯工作單元,類似於 controller 在
正常的 MVC 設定。例如,您可以有一個 ChatChannel
和
一個 AppearancesChannel
,消費者可以訂閱
或這兩個頻道。至少,應該訂閱一個消費者
到一個頻道。
2.4 訂閱者
當消費者訂閱頻道時,他們充當訂閱者。 訂閱者和頻道之間的連線是驚喜, 稱為訂閱。消費者可以充當給定頻道的訂閱者 任意次數。例如,消費者可以訂閱多個聊天室 同時。 (請記住,一個物理使用者可能有多個消費者, 每個頁籤/裝置對您的連線開啟一個)。
2.5 釋出/訂閱
釋出/訂閱 或 釋出-訂閱是指一種訊息佇列正規化,其中的傳送者 資訊(釋出者),將資料傳送到接收者的抽象類 (訂閱者),而不指定單個收件人。 Action Cable 使用這個 在伺服器和許多客戶端之間進行通訊的方法。
2.6 廣播
廣播是一個釋出/訂閱連結,廣播公司傳輸的任何內容都在其中 直接傳送給正在流式傳輸該廣播的頻道訂閱者。 每個頻道可以流式傳輸零個或多個廣播。
3 伺服器端元件
3.1 連線
對於伺服器接受的每個 WebSocket,都會實例化一個連線物件。這 物件成為所有建立的頻道訂閱的父級 從此。連線本身不處理任何特定的應用程式 超出身份驗證和授權的邏輯。 WebSocket 的客戶端 連線被稱為連線消費者。個人使用者將建立 他們開啟的每個瀏覽器頁籤、視窗或裝置都有一個消費者連線對。
連線是 ApplicationCable::Connection
的實例,它擴充套件了
[ActionCable::Connection::Base
][]。在ApplicationCable::Connection
,您
授權傳入連線並繼續建立它,如果使用者可以
被識別。
3.1.1 連線設定
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
這裡 [identified_by
][] 指定一個連線識別符號,可用於查詢
具體聯絡稍後。請注意,任何標記為識別符號的內容都會自動
在透過連線建立的任何通道實例上建立一個同名的委託。
此示例依賴於您已經處理了使用者身份驗證的事實 應用程式中的其他位置,並且成功的身份驗證設定了加密 cookie 與使用者 ID。
cookie 然後在新連線時自動傳送到連線實例
已嘗試,您可以使用它來設定 current_user
。透過識別連線
透過同一當前使用者,您還確保以後可以檢索所有開啟的
給定使用者的連線(如果使用者被刪除,可能會斷開所有連線
或未經授權)。
如果您的身份驗證方法包括使用 session,則使用 cookie 儲存作為
session,你的session cookie名為_session
,使用者ID key是user_id
你
可以使用這種方法:
verified_user = User.find_by(id: cookies.encrypted['_session']['user_id'])
3.1.2 異常處理
預設情況下,未處理的異常會被捕獲並記錄到 Rails 的記錄器中。如果你願意
全域性攔截這些異常並將它們報告給外部錯誤跟蹤服務,以便
例如,您可以使用 rescue_from
執行此操作:
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
rescue_from StandardError, with: :report_error
private
def report_error(e)
SomeExternalBugtrackingService.notify(e)
end
end
end
3.2 頻道
channel 封裝了一個邏輯工作單元,類似於 controller 在
正常MVC設定。預設情況下,Rails 建立一個父 ApplicationCable::Channel
類
(擴充套件 [ActionCable::Channel::Base
][])用於封裝通道之間的共享邏輯。
3.2.1 父頻道設定
# app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
然後,您將建立自己的頻道類。例如,你可以有一個
ChatChannel
和 AppearanceChannel
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
然後消費者可以訂閱這些頻道中的一個或兩個。
3.2.2 訂閱
消費者訂閱頻道,充當訂閱者。他們的聯絡是 稱為訂閱。然後將生成的訊息路由到這些通道 基於頻道消費者傳送的識別符號的訂閱。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Called when the consumer has successfully
# become a subscriber to this channel.
def subscribed
end
end
3.2.3 異常處理
與 ApplicationCable::Connection
一樣,您也可以在
處理引發異常的特定通道:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
rescue_from 'MyError', with: :deliver_error_message
private
def deliver_error_message(e)
broadcast_to(...)
end
end
4 客戶端元件
4.1 連線
消費者需要他們這邊的連線實例。這可以 使用以下 JavaScript 建立,該 JavaScript 預設由 Rails 生成:
4.1.1 連線消費者
// app/javascript/channels/consumer.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
這將準備好一個消費者,該消費者將預設連線到您伺服器上的 /cable
。
除非您還指定了至少一個訂閱,否則不會建立連線
你有興趣擁有。
消費者可以選擇使用一個引數來指定要連線到的 URL。這 可以是字串或返回字串的函式,該字串將在 WebSocket 已開啟。
// Specify a different URL to connect to
createConsumer('https://ws.example.com/cable')
// Use a function to dynamically generate the URL
createConsumer(getWebSocketURL)
function getWebSocketURL() {
const token = localStorage.get('auth-token')
return `https://ws.example.com/cable?token=${token}`
}
4.1.2 訂閱者
消費者透過建立對給定頻道的訂閱成為訂閱者:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "AppearanceChannel" })
雖然這會建立訂閱,但回應所需的功能 接收到的資料將在後面描述。
消費者可以多次充當給定頻道的訂閱者。為了 例如,消費者可以同時訂閱多個聊天室:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
5 客戶端-伺服器 Interactions
5.1 流
Streams 提供頻道路由已釋出內容的機制
(廣播)給他們的訂閱者。例如,以下程式碼使用
[stream_from
][]訂閱名為chat_Best Room
的廣播,當
:room
引數的 value 是 "Best Room"
:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
然後,在 Rails 應用程式的其他地方,您可以透過以下方式向這樣的房間廣播
呼叫 [broadcast
][]:
ActionCable.server.broadcast("chat_Best Room", { body: "This Room is Best Room." })
如果您有一個與 model 相關的流,則廣播名稱
可以從通道和model生成。例如,下面的程式碼
使用 [stream_for
][] 訂閱廣播,如
comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
,其中 Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
是
帖子 model 的 GlobalID。
class CommentsChannel < ApplicationCable::Channel
def subscribed
post = Post.find(params[:id])
stream_for post
end
end
然後你可以透過呼叫 [broadcast_to
][] 來廣播到這個頻道:
CommentsChannel.broadcast_to(@post, @comment)
5.2 廣播
廣播是一個釋出/訂閱連結,其中釋出者傳輸的任何內容 直接路由到正在流式傳輸的頻道訂閱者 廣播。每個頻道可以流式傳輸零個或多個廣播。
廣播純粹是一個線上佇列並且依賴於時間。如果消費者是 不流式傳輸(訂閱給定頻道),他們將無法獲得廣播 如果他們稍後連線。
5.3 訂閱
當消費者訂閱頻道時,他們充當訂閱者。這 連線稱為訂閱。傳入的訊息然後被路由到 這些頻道訂閱基於有線電視消費者傳送的識別符號。
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
5.4 向通道傳遞引數
您可以在建立一個時從客戶端傳遞引數到伺服器端 訂閱。例如:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
作為第一個引數傳遞給 subscriptions.create
的物件成為
引數在有線頻道中雜湊。 keyword channel
是必需的:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
# 在你的應用程式的某個地方,這被稱為,也許
# 來自 NewCommentJob。
ActionCable.server.broadcast(
"chat_#{room}",
{
sent_by: 'Paul',
body: 'This is a cool chat app.'
}
)
5.5 轉發訊息
一個常見的用例是重新廣播一個客戶端傳送的訊息給任何 其他連線的客戶端。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
def receive(data)
ActionCable.server.broadcast("chat_#{params[:room]}", data)
end
end
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
// data => { sent_by: "Paul", body: "This is a cool chat app." }
}
}
chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
轉播將被所有連線的客戶端接收,包括 傳送訊息的客戶端。請注意,引數與它們時相同 你訂閱了頻道。
6 全棧示例
以下設定步驟對兩個示例都是通用的:
6.1 示例 1:使用者外觀
這是一個跟蹤使用者是否線上的頻道的簡單示例 以及他們在哪個頁面上。 (這對於建立狀態功能非常有用,例如顯示 如果使用者線上,則使用者名稱旁邊會顯示一個綠點)。
建立服務端外觀通道:
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
def subscribed
current_user.appear
end
def unsubscribed
current_user.disappear
end
def appear(data)
current_user.appear(on: data['appearing_on'])
end
def away
current_user.away
end
end
當啟動訂閱時,subscribed
callback 被觸發,我們
藉此機會說“當前使用者確實出現了”。那
出現/消失 API 可以由 Redis、資料庫或其他任何東西支援。
建立客戶端外觀頻道訂閱:
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("AppearanceChannel", {
// Called once when the subscription is created.
initialized() {
this.update = this.update.bind(this)
},
// Called when the subscription is ready for use on the server.
connected() {
this.install()
this.update()
},
// Called when the WebSocket connection is closed.
disconnected() {
this.uninstall()
},
// Called when the subscription is rejected by the server.
rejected() {
this.uninstall()
},
update() {
this.documentIsActive ? this.appear() : this.away()
},
appear() {
// Calls `AppearanceChannel#appear(data)` on the server.
this.perform("appear", { appearing_on: this.appearingOn })
},
away() {
// Calls `AppearanceChannel#away` on the server.
this.perform("away")
},
install() {
window.addEventListener("focus", this.update)
window.addEventListener("blur", this.update)
document.addEventListener("turbolinks:load", this.update)
document.addEventListener("visibilitychange", this.update)
},
uninstall() {
window.removeEventListener("focus", this.update)
window.removeEventListener("blur", this.update)
document.removeEventListener("turbolinks:load", this.update)
document.removeEventListener("visibilitychange", this.update)
},
get documentIsActive() {
return document.visibilityState == "visible" && document.hasFocus()
},
get appearingOn() {
const element = document.querySelector("[data-appearing-on]")
return element ? element.getAttribute("data-appearing-on") : null
}
})
6.1.1 客戶端-伺服器 Interaction
Client 透過
App.cable = 連線到**Server** ActionCable.createConsumer("ws://cable.example.com")ActionCablecable.js
)。這 Server 透過current_user
標識此連線。Client 透過以下方式訂閱外觀頻道
consumer.subscriptions.create({ channel: "AppearanceChannel" })
。 (appearance_channel.js
)Server 識別出一個新的訂閱已經啟動 外觀頻道並執行其
subscribed
callback,呼叫appear
current_user
上的方法。 (appearance_channel.rb
)Client 識別到訂閱已經建立並呼叫
connected
(appearance_channel.js
) 依次呼叫install
和appear
。appear
呼叫伺服器上的AppearanceChannel#appear(data)
,並提供一個{ appearing_on: this.appearingOn }
的資料雜湊。這是 可能是因為伺服器端通道實例會自動公開所有 在類上宣告的公共方法(減去 callbacks),以便這些可以 透過訂閱的perform
方法作為遠端過程呼叫到達。Server 在外觀上接收到
appear
action 的請求 由current_user
標識的連線的通道 (appearance_channel.rb
)。 伺服器 檢索資料:appearing_on
key 來自資料雜湊並將其設定為:on
的 value key 被傳遞給current_user.appear
。
6.2 示例 2:接收新的 Web 通知
外觀示例是關於將伺服器功能暴露給 透過 WebSocket 連線的客戶端呼叫。但偉大的事情 關於 WebSockets 是一條雙向街道。所以現在讓我們展示一個例子 伺服器在客戶端呼叫 action 的地方。
這是一個 Web 通知通道,可讓您觸發客戶端 當您廣播到正確的流時的網路通知:
建立伺服器端 Web 通知通道:
# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end
建立客戶端 Web 通知頻道訂閱:
// app/javascript/channels/web_notifications_channel.js
// Client-side which assumes you've already requested
// the right to send web notifications.
import consumer from "./consumer"
consumer.subscriptions.create("WebNotificationsChannel", {
received(data) {
new Notification(data["title"], body: data["body"])
}
})
從您的其他位置向 Web 通知通道實例廣播內容 應用:
# 在你的應用程式中的某個地方被呼叫,可能來自 NewCommentJob
WebNotificationsChannel.broadcast_to(
current_user,
title: 'New things!',
body: 'All the news fit to print'
)
WebNotificationsChannel.broadcast_to
呼叫將訊息放入當前
訂閱介面卡的 pubsub 佇列在每個單獨的廣播名稱下
使用者。對於 ID 為 1 的使用者,廣播名稱將是
web_notifications:1
。
該頻道已被指示流式傳輸到達的所有內容
web_notifications:1
透過呼叫 received
直接到客戶端
callback。作為引數傳遞的資料是作為第二個引數傳送的雜湊
到伺服器端廣播呼叫,JSON 編碼用於跨線路的行程
併為作為 received
到達的資料引數解包。
6.3 更完整的例子
參見 rails/actioncable-examples 有關如何在 Rails 應用程式中設定 Action Cable 和新增頻道的完整示例的儲存庫。
7 配置
Action Cable 有兩個必需的配置:訂閱介面卡和允許的請求源。
7.1 訂閱介面卡
預設情況下,Action Cable 在 Action Cable 中查詢配置檔案。 該檔案必須為每個 Rails 環境指定一個介面卡。見 Dependencies 部分提供了有關介面卡的其他資訊。
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: redis://10.10.3.153:6381
channel_prefix: appname_production
7.1.1 介面卡配置
以下是可供終端使用者使用的訂閱介面卡列表。
7.1.1.1 非同步介面卡
非同步介面卡用於開發/測試,不應在生產中使用。
7.1.1.2 Redis 介面卡
Redis 介面卡要求使用者提供指向 Redis 伺服器的 URL。
此外,可以提供 channel_prefix
以避免頻道名稱衝突
為多個應用程式使用同一個 Redis 伺服器時。有關更多詳細資訊,請參閱 Redis PubSub 文件。
Redis 介面卡還支援 SSL/TLS 連線。需要的 SSL/TLS 引數可以在配置 yaml 檔案中的 ssl_params
key 中傳遞。
production:
adapter: redis
url: rediss://10.10.3.153:tls_port
channel_prefix: appname_production
ssl_params: {
ca_file: "/path/to/ca.crt"
}
提供給 ssl_params
的選項直接傳遞給 OpenSSL::SSL::SSLContext#set_params
方法,並且可以是 SSL 上下文的任何有效屬性。
有關其他可用屬性,請參閱 OpenSSL::SSL::SSLContext 文件。
如果您在防火牆後為 redis 介面卡使用自簽名證書並選擇跳過證書檢查,則 ssl verify_mode
應設定為 OpenSSL::SSL::VERIFY_NONE
。
警告:除非您完全瞭解安全隱患,否則不建議在生產中使用 VERIFY_NONE
。為了為 Redis 介面卡設定這個選項,配置應該是 ssl_params: { verify_mode: <%= OpenSSL::SSL::VERIFY_NONE %> }
。
7.1.1.3 PostgreSQL 介面卡
PostgreSQL 介面卡使用 Active Record 的連線池,因此
應用程式的 config/database.yml
資料庫配置,用於其連線。
這在未來可能會改變。 #27214
7.2 允許的請求來源
Action Cable 將只接受來自指定來源的請求,它們是 作為陣列傳遞給伺服器配置。起源可以是 將執行匹配檢查的字串或正則表示式。
config.action_cable.allowed_request_origins = ['https://rubyonrails.com', %r{http://ruby.*}]
禁用和允許來自任何來源的請求:
config.action_cable.disable_request_forgery_protection = true
預設情況下,Action Cable 在執行時允許來自 localhost:3000 的所有請求 在開發環境中。
7.3 消費者配置
要配置 URL,請在 HTML 佈局中新增對 [action_cable_meta_tag
][] 的呼叫
頭。這使用通常透過 config.action_cable.url
設定的 URL 或路徑
環境配置檔案。
7.4 工作池配置
工作池用於執行連線 callbacks 和通道 actions 與伺服器的主執行緒隔離。 Action Cable 允許應用 配置工作池中同時處理的執行緒數。
config.action_cable.worker_pool_size = 4
另外請注意,您的伺服器必須提供至少相同數量的資料庫
連線,因為你有工人。預設工作池大小設定為 4,因此
這意味著您必須至少提供 4 個可用的資料庫連線。
您可以透過 pool
屬性在 config/database.yml
中更改它。
7.5 客戶端日誌
預設情況下禁用客戶端日誌記錄。您可以透過將 ActionCable.logger.enabled
設定為 true 來啟用此功能。
import * as ActionCable from '@rails/actioncable'
ActionCable.logger.enabled = true
7.6 其他配置
另一個常見的配置選項是應用到 每個連線記錄器。這是一個使用的示例 使用者帳戶 ID(如果可用),否則在標記時為“無帳戶”:
config.action_cable.log_tags = [
-> request { request.env['user_account_id'] || "no-account" },
:action_cable,
-> request { request.uuid }
]
有關所有配置選項的完整列表,請參閱
ActionCable::Server::Configuration
類。
8 執行獨立的有線伺服器
8.1 應用內
Action Cable 可以與您的 Rails 應用程式一起執行。例如,到
偵聽 /websocket
上的 WebSocket 請求,指定路徑到
config.action_cable.mount_path
:
# 配置/應用程式.rb
class Application < Rails::Application
config.action_cable.mount_path = '/websocket'
end
您可以使用 ActionCable.createConsumer()
連線到電纜
伺服器,如果在佈局中呼叫 action_cable_meta_tag
。否則,A 路徑為
指定為 createConsumer
的第一個引數(例如 ActionCable.createConsumer("/websocket")
)。
對於伺服器的每個實例,您都可以為每個工作人員建立您的伺服器 生成時,您還將擁有一個新的 Action Cable 實例,但是 Redis 或 PostgreSQL 介面卡使訊息跨連線保持同步。
8.2 獨立
電纜伺服器可以與您的普通應用程式伺服器分開。它是 仍然是一個 Rack 應用程式,但它是它自己的 Rack 應用程式。推薦的 基本設定如下:
# 電纜/config.ru
require_relative "../config/environment"
Rails.application.eager_load!
run ActionCable.server
然後使用 bin/cable
ala 中的 binstub 啟動伺服器:
#!/bin/bash
bundle exec puma -p 28080 cable/config.ru
以上將在埠 28080 上啟動有線伺服器。
8.3 備註
WebSocket 伺服器無權訪問 session,但它有 訪問 cookies。這個可以在需要處理的時候使用 驗證。您可以在這篇 文章 中看到使用 Devise 執行此操作的一種方法。
9 依賴
Action Cable 提供了一個訂閱介面卡介面來處理它的
pubsub內部結構。預設情況下,非同步、內聯、PostgreSQL 和 Redis
包括介面卡。預設介面卡
在新的 Rails 應用程式中是非同步 (async
) 介面卡。
Ruby 方面建立在 websocket-driver 之上, nio4r 和 concurrent-ruby。
10 部署
Action Cable 由 WebSocket 和執行緒的組合提供支援。這倆 框架管道和使用者指定的通道工作由內部處理 利用 Ruby 的本機執行緒支援。這意味著您可以使用所有正常 Rails models 沒有問題,只要你沒有犯任何執行緒安全罪。
Action Cable伺服器實現了Rack socket劫持API, 從而允許使用多執行緒模式來管理連線 在內部,無論應用伺服器是否是多執行緒的。
因此,Action Cable 與 Unicorn、Puma 和 乘客。
11 測試
您可以找到有關如何測試 Action Cable 功能的詳細說明 測試指南。
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。