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

引擎入門

在本指南中,您將瞭解引擎以及如何使用它們來提供 透過一個乾淨和非常 易於使用的介面。

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

1 什麼是發動機?

引擎可以被認為是提供功能的微型應用程式 他們的主機應用程式。 Rails 應用實際上只是一個“增壓” 引擎,其中 Rails::Application 類繼承了它的許多行為 來自 Rails::Engine

因此,引擎和應用程式幾乎可以被認為是一回事, 正如您將在本指南中看到的那樣,只是有細微的差別。發動機和 應用程式也共享一個共同的結構。

引擎也與外掛密切相關。兩者共享一個共同的lib 目錄結構,並且都是使用 rails plugin new 產生的 發電機。不同之處在於引擎被視為“完整外掛” Rails(由傳遞給產生器的 --full 選項指示 命令)。我們實際上將在這裡使用 --mountable 選項,其中包括 --full 的所有功能,然後是一些。本指南將參考這些 “完整的外掛”在整個過程中只是作為“引擎”。引擎可以是外掛, 一個外掛可以是一個引擎。

本指南中將建立的引擎將被稱為“blorgh”。這 引擎將為其主機應用程式提供部落格功能,允許 用於建立新文章和評論。在本指南的開頭,您 將僅在引擎本身內工作,但在後面的部分中,您將 瞭解如何將其掛接到應用程式中。

引擎也可以與其宿主應用程式隔離。這意味著一個 應用程式能夠擁有由路由 helper 提供的路徑,例如 articles_path 並使用一個引擎,該引擎也提供了一個路徑,也稱為 articles_path,兩者不會衝突。與此同時,controllers、models 和表名也是名稱空間的。稍後您將看到如何執行此操作 指導。

重要的是要始終牢記應用程式應該 始終優先於其引擎。應用程式是物件 對其環境中發生的事情有最終決定權。發動機應 只是加強它,而不是徹底改變它。

要檢視其他引擎的演示,請檢視 設計,一個提供 對其父應用程式的身份驗證,或 Thredded,一個提供論壇的引擎 功能。還有 Spree 提供電子商務平臺,以及 煉油CMS,一個CMS引擎。

最後,如果沒有詹姆斯亞當的工作,引擎是不可能出現的, Piotr Sarnacki、Rails 核心團隊和其他一些人。如果你曾經 遇見他們,別忘了說聲謝謝!

2 產生引擎

要產生引擎,您需要執行外掛產生器並傳遞它 根據需要選擇合適的選項。對於“blorgh”示例,您需要 建立一個“可安裝”引擎,在終端中執行此命令:

$ rails plugin new blorgh --mountable

可以透過鍵入以下內容檢視外掛產生器的完整選項列表:

$ rails plugin --help

--mountable 選項告訴產生器您要建立一個 “可安裝”和名稱空間隔離引擎。該產生器將提供相同的 骨架結構與 --full 選項一樣。 --full 選項告訴 要建立引擎的產生器,包括骨架結構 這提供了以下內容:

  • app 目錄樹
  • config/routes.rb 檔案:

    Rails.application.routes.draw do
    end
    
  • 位於 lib/blorgh/engine.rb 的檔案,其功能與 標準 Rails 應用程式的 config/application.rb 檔案:

    module Blorgh
      class Engine < ::Rails::Engine
      end
    end
    

--mountable 選項將新增到 --full 選項中:

  • 資產清單檔案(blorgh_manifest.jsapplication.css
  • 名稱空間的 ApplicationController 存根
  • 名稱空間的 ApplicationHelper 存根
  • 引擎的佈局 view 模板
  • 名稱空間隔離到 config/routes.rb

    Blorgh::Engine.routes.draw do
    end
    
  • 名稱空間隔離到 lib/blorgh/engine.rb

    module Blorgh
      class Engine < ::Rails::Engine
        isolate_namespace Blorgh
      end
    end
    

此外,--mountable 選項告訴產生器安裝引擎 在位於 test/dummy 的虛擬測試應用程式中新增 跟隨虛擬應用程式的路由檔案在 test/dummy/config/routes.rb

mount Blorgh::Engine => "/blorgh"

2.1 引擎內部

2.1.1 關鍵檔案

在這個全新引擎目錄的根目錄下有一個 blorgh.gemspec 檔案。 當您稍後將引擎包含到應用程式中時,您將使用 Rails 應用程式的 Gemfile 中的這一行:

gem 'blorgh', path: 'engines/blorgh'

不要忘記像往常一樣執行 bundle install。透過將其指定為 gem 內 Gemfile,Bundler 會載入它,解析這個 blorgh.gemspec 檔案 並且需要 lib 目錄中的一個名為 lib/blorgh.rb 的檔案。這 檔案需要 blorgh/engine.rb 檔案(位於 lib/blorgh/engine.rb) 並定義了一個名為 Blorgh 的基礎 module。

require "blorgh/engine"

module Blorgh
end

提示:有些引擎選擇使用這個檔案來放置全域性設定選項 為他們的引擎。這是一個相對不錯的主意,所以如果你想提供 設定選項,定義引擎的 module 的檔案是 完美的。將方法放在 module 中,您就可以開始使用了。

lib/blorgh/engine.rb 中是引擎的基類:

module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
  end
end

透過繼承 Rails::Engine 類,這個 gem 通知 Rails 在指定路徑有一個引擎,並且會正確安裝引擎 在應用程式內部,執行諸如新增 app 目錄的任務 引擎到 models、郵件程式、controllers 和 views 的載入路徑。

這裡的 isolate_namespace 方法值得特別注意。這個電話是 負責隔離 controllers、models、路由等 他們自己的名稱空間,遠離應用程式內部的類似元件。 沒有這個,發動機的部件就有可能“洩漏” 進入應用程式,導致不必要的中斷,或那個重要的引擎 元件可以被應用程式中類似命名的東西覆蓋。 此類衝突的示例之一是 helpers。不打電話 isolate_namespace,引擎的 helpers 將包含在應用程式的 controllers。

強烈建議保留 isolate_namespace 線 在 Engine 類定義中。沒有它,引擎中產生的類 可能與應用程式衝突。

名稱空間的這種隔離意味著呼叫產生的 model 到bin/rails generate model,比如bin/rails generate model article,就不會叫Article,而是 取而代之的是名稱空間並稱為 Blorgh::Article。此外,該表為 model 是名稱空間的,變成了 blorgh_articles,而不是簡單的 articles。 類似於 model 名稱空間,一個名為 ArticlesController 的 controller 變成 Blorgh::ArticlesController 和該 controller 的 views 不會在 app/views/articles,而是 app/views/blorgh/articles。郵寄、工作 和 helpers 也是名稱空間的。

最後,路由也將在引擎內隔離。這是最 關於名稱空間的重要部分,稍後將在 本指南的 路線 部分。

2.1.2 app 目錄

app目錄裡面是標準的assetscontrollershelpers、 您應該熟悉的 jobsmailersmodelsviews 目錄 從應用程式。當我們編寫引擎時,我們將在以後的部分中更多地研究 models。

app/assets 目錄中,有 imagesstylesheets 目錄,您應該再次熟悉,因為它們 應用程式的相似性。然而,這裡的一個差別是,每個 directory 包含一個帶有引擎名稱的子目錄。因為這個引擎是 將要名稱空間,它的資產也應該是。

app/controllers 目錄中有一個 blorgh 目錄 包含一個名為 application_controller.rb 的檔案。該檔案將提供任何 引擎的 controllers 的通用功能。 blorgh 目錄 是引擎的另一個 controllers 所在的位置。透過將它們放在 這個名稱空間目錄,你可以防止它們與 同名的 controllers 在其他引擎中甚至在 應用。

引擎內的 ApplicationController 類的命名就像 Rails 應用程式,以便您更輕鬆地轉換您的 應用到引擎中。

如果父應用程式在 classic 模式下執行,您可能會遇到 您的引擎 controller 從主應用程式繼承的情況 controller 而不是您引擎的應用程式 controller。最好的預防方法 這是在父應用程式中切換到 zeitwerk 模式。否則,使用 require_dependency 以確保引擎的應用程式 controller 是 載入。例如:

# 僅在 `classic` 模式下需要。
require_dependency "blorgh/application_controller"

module Blorgh
  class ArticlesController < ApplicationController
    # ...
  end
end

不要使用 require 因為它會破壞自動重新載入 開發環境中的類 - 使用 require_dependency 確保 類以正確的方式載入和解除安裝。

就像 app/controllers 一樣,你會在下面找到一個 blorgh 子目錄 app/helpersapp/jobsapp/mailersapp/models 目錄 包含關聯的 application_*.rb 檔案,用於收集常見的 功能。透過將您的檔案放在這個子目錄和名稱空間下 你的物件,你可以防止它們與同名的物件發生衝突 其他引擎甚至應用程式中的元素。

最後,app/views 目錄包含一個 layouts 資料夾,其中包含一個 檔案位於 blorgh/application.html.erb。此檔案允許您指定佈局 對於發動機。如果將此引擎用作獨立引擎,那麼您 將在此檔案中為其佈局新增任何自定義,而不是 應用程式的 app/views/layouts/application.html.erb 檔案。

如果您不想對引擎的使用者強制佈局,那麼您可以 刪除此檔案並在您的 controllers 中引用不同的佈局 引擎。

2.1.3 bin 目錄

該目錄包含一個檔案 bin/rails,它使您能夠使用 rails 子命令和產生器,就像您在應用程式中一樣。 這意味著您將能夠為此產生新的 controllers 和 models 透過執行這樣的命令可以很容易地引擎引擎:

$ bin/rails generate model

當然,請記住,使用這些命令產生的任何內容 在 Engine 類中具有 isolate_namespace 的引擎將被名稱空間。

2.1.4 test 目錄

test 目錄是對引擎進行測試的地方。為了測試發動機, 有一個 Rails 應用程式的縮減版本嵌入其中 test/dummy。此應用程式將發動機安裝在 test/dummy/config/routes.rb 檔案:

Rails.application.routes.draw do
  mount Blorgh::Engine => "/blorgh"
end

此行將引擎安裝在路徑 /blorgh 處,這將使其可訪問 僅在該路徑上透過應用程式。

test目錄裡面有test/integration目錄,其中 應該放置引擎的整合測試。其他目錄可以 也在 test 目錄中建立。例如,您可能希望建立一個 test/models 目錄用於您的 model 測試。

3 提供引擎功能

本指南包含的引擎提供提交文章和評論 功能並遵循與 Getting Started 指南,有一些新的曲折。

對於本節,請確保在根目錄中執行命令 blorgh 引擎的目錄。

3.1 產生文章資源

為部落格引擎產生的第一件事是 Article model 和相關 controller。要快速產生它,您可以使用 Rails scaffold 產生器。

$ bin/rails generate scaffold article title:string text:text

此命令將輸出以下資訊:

invoke  active_record
create    db/migrate/[timestamp]_create_blorgh_articles.rb
create    app/models/blorgh/article.rb
invoke    test_unit
create      test/models/blorgh/article_test.rb
create      test/fixtures/blorgh/articles.yml
invoke  resource_route
 route    resources :articles
invoke  scaffold_controller
create    app/controllers/blorgh/articles_controller.rb
invoke    erb
create      app/views/blorgh/articles
create      app/views/blorgh/articles/index.html.erb
create      app/views/blorgh/articles/edit.html.erb
create      app/views/blorgh/articles/show.html.erb
create      app/views/blorgh/articles/new.html.erb
create      app/views/blorgh/articles/_form.html.erb
invoke    test_unit
create      test/controllers/blorgh/articles_controller_test.rb
create      test/system/blorgh/articles_test.rb
invoke    helper
create      app/helpers/blorgh/articles_helper.rb
invoke      test_unit

scaffold 產生器做的第一件事就是呼叫 active_record 產生器,它為資源產生一個 migration 和一個 model。注意這裡, 然而,migration 被稱為 create_blorgh_articles 而不是 通常的 create_articles。這是由於呼叫了 isolate_namespace 方法 Blorgh::Engine 類的定義。這裡的 model 也是名稱空間的, 被放置在 app/models/blorgh/article.rb 而不是 app/models/article.rb 由於 到 Engine 類中的 isolate_namespace 呼叫。

接下來為這個model呼叫test_unit產生器,產生一個model 在 test/models/blorgh/article_test.rb 測試(而不是 test/models/article_test.rb) 和 test/fixtures/blorgh/articles.yml 的固定裝置 (而不是 test/fixtures/articles.yml)。

之後,將資源的一行插入到 config/routes.rb 檔案中 對於發動機。這一行就是 resources :articles,把 引擎的 config/routes.rb 檔案變成這樣:

Blorgh::Engine.routes.draw do
  resources :articles
end

請注意,這裡的路線是在 Blorgh::Engine 物件上繪製的,而不是 YourApp::Application 類。這是為了限制發動機路線 到發動機本身,可以安裝在特定點,如圖所示 測試目錄 部分。它還導致引擎的路由 與應用程式中的那些路由隔離。這 本指南的 Routes 部分對其進行了詳細描述。

接下來呼叫scaffold_controller產生器,產生一個controller 稱為 Blorgh::ArticlesController(在 app/controllers/blorgh/articles_controller.rb) 及其相關的 views 在 app/views/blorgh/articles。此產生器還產生測試 controller(test/controllers/blorgh/articles_controller_test.rbtest/system/blorgh/articles_test.rb)和一個 helper(app/helpers/blorgh/articles_helper.rb)。

這個產生器建立的所有東西都被整齊地命名。 controller 的 類在 Blorgh module 中定義:

module Blorgh
  class ArticlesController < ApplicationController
    # ...
  end
end

ArticlesController 類繼承自 Blorgh::ApplicationController,不是應用程式的 ApplicationController

app/helpers/blorgh/articles_helper.rb 中的 helper 也是名稱空間的:

module Blorgh
  module ArticlesHelper
    # ...
  end
end

這有助於防止與可能具有的任何其他引擎或應用程式發生衝突 文章資源也是如此。

您可以透過在根目錄執行 bin/rails db:migrate 來檢視引擎到目前為止的內容 我們的引擎執行由 scaffold 產生器產生的 migration,然後 在 test/dummy 中執行 bin/rails server。當你開啟 http://localhost:3000/blorgh/articles 你會看到預設的 scaffold 被產生。點選周圍!你剛剛產生了你的第一個引擎 職能。

如果您更願意在控制檯中玩耍,bin/rails console 也可以正常工作 就像一個 Rails 應用程式。記住: Article model 是名稱空間的,所以要 引用它,您必須將其稱為 Blorgh::Article

irb> Blorgh::Article.find(1)
=> #<Blorgh::Article id: 1 ...>

最後一件事是該引擎的 articles 資源應該是根 的發動機。每當有人進入引擎所在的根路徑時 安裝後,它們應該顯示一個文章列表。這可以發生,如果 這一行被插入到引擎內的 config/routes.rb 檔案中:

root to: "articles#index"

現在人們只需要到引擎的根目錄就能看到所有的文章,而不是訪問 /articles。這意味著,而不是 http://localhost:3000/blorgh/articles,你只需要去 http://localhost:3000/blorgh/articles,你只需要去 http://localhost:3000/blorgh 現在。

3.2 產生評論資源

既然引擎可以建立新文章,那麼新增才有意義 評論功能也是如此。為此,您需要產生評論 model,評論controller,然後修改文章scaffold顯示 評論並允許人們建立新的評論。

從引擎根目錄執行 model 產生器。告訴它產生一個 Comment model,相關表有兩列:一個 article_id 整數 和 text 文字列。

$ bin/rails generate model Comment article_id:integer text:text

這將輸出以下內容:

invoke  active_record
create    db/migrate/[timestamp]_create_blorgh_comments.rb
create    app/models/blorgh/comment.rb
invoke    test_unit
create      test/models/blorgh/comment_test.rb
create      test/fixtures/blorgh/comments.yml

這個產生器呼叫將只產生它需要的必要的 model 檔案, 命名 blorgh 目錄下的檔案並建立一個 model 類 稱為 Blorgh::Comment。現在執行 migration 來建立我們的 blorgh_comments 桌子:

$ bin/rails db:migrate

要顯示對文章的評論,請編輯 app/views/blorgh/articles/show.html.erb 和 在“編輯”連結之前新增此行:

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

這一行將要求有一個 has_many association 來定義註釋 在 Blorgh::Article model 上,現在沒有。要定義一個,開啟 app/models/blorgh/article.rb 並將此行新增到 model 中:

has_many :comments

把 model 變成這樣:

module Blorgh
  class Article < ApplicationRecord
    has_many :comments
  end
end

因為 has_many 是在一個類中定義的 Blorgh module, Rails 會知道你要使用Blorgh::Comment model 用於這些物件,因此無需指定使用 :class_name 選項在這裡。

接下來,需要有一個表單,以便可以在文章上建立評論。到 新增這個,把這一行放在對 render @article.comments 的呼叫下面 app/views/blorgh/articles/show.html.erb

<%= render "blorgh/comments/form" %>

接下來,該行將呈現的部分需要存在。建立一個新的 app/views/blorgh/comments 中的目錄,其中有一個名為 _form.html.erb 具有以下內容來建立所需的部分:

<h3>New comment</h3>
<%= form_with model: [@article, @article.comments.build] do |form| %>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <%= form.submit %>
<% end %>

提交此表單時,它將嘗試執行 POST 請求 到引擎內的 /articles/:article_id/comments 路線。這條路線不 目前存在,但可以通過更改 resources :articles 行來建立 在 config/routes.rb 內進入這些行:

resources :articles do
  resources :comments
end

這為評論建立了一個巢狀的路由,這正是表單所要求的。

該路由現在存在,但該路由去往的 controller 不存在。到 建立它,從引擎根執行此命令:

$ bin/rails generate controller comments

這將產生以下內容:

create  app/controllers/blorgh/comments_controller.rb
invoke  erb
 exist    app/views/blorgh/comments
invoke  test_unit
create    test/controllers/blorgh/comments_controller_test.rb
invoke  helper
create    app/helpers/blorgh/comments_helper.rb
invoke    test_unit

該表單將向 /articles/:article_id/comments 發出 POST 請求,其中 將對應於 Blorgh::CommentsController 中的 create action。這 需要建立action,可以通過放入以下幾行 在 app/controllers/blorgh/comments_controller.rb 中的類定義中:

def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.create(comment_params)
  flash[:notice] = "Comment has been created!"
  redirect_to articles_path
end

private
  def comment_params
    params.require(:comment).permit(:text)
  end

這是使新的評論表單工作所需的最後一步。顯示 然而,這些評論還不完全正確。如果您要建立評論 現在,您會看到此錯誤:

Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder],
:formats=>[:html], :locale=>[:en, :en]}. Searched in:   *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"   *
"/Users/ryan/Sites/side_projects/blorgh/app/views"

引擎無法找到呈現評論所需的部分。 Rails 首先在應用程式的 (test/dummy) app/views 目錄中查詢,然後 然後在引擎的 app/views 目錄中。當它找不到它時,它會丟擲 這個錯誤。引擎知道尋找 blorgh/comments/_comment 因為 它接收的 model 物件來自 Blorgh::Comment 類。

目前,該部分將僅負責渲染評論文字。 在 app/views/blorgh/comments/_comment.html.erb 建立一個新檔案並將其放入 裡面的行:

<%= comment_counter + 1 %>. <%= comment.text %>

comment_counter 區域性變數由 <%= 渲染器提供給我們 @article.comments %> 呼叫,它將自動定義它並增加 計數器,因為它遍歷每個評論。在這個例子中它用於 建立時在每條評論旁邊顯示一個小數字。

這樣就完成了部落格引擎的評論功能。現在是時候使用了 它在一個應用程式中。

4 連線到應用程式

在應用程式中使用引擎非常容易。本節介紹如何 將引擎安裝到應用程式和所需的初始設定中,以及 將引擎連結到應用程式提供的 User 類以提供 引擎內文章和評論的所有權。

4.1 安裝發動機

首先,需要在應用程式的 Gemfile 中指定引擎。如果 沒有一個應用程式可以方便地進行測試,請使用 引擎目錄外的 rails new 命令如下:

$ rails new unicorn

通常,在 Gemfile 中指定引擎將通過指定它來完成 作為普通的日常寶石。

gem 'devise'

但是,因為您是在本地機器上開發 blorgh 引擎, 您需要在 Gemfile 中指定 :path 選項:

gem 'blorgh', path: 'engines/blorgh'

然後執行 ​​bundle 來安裝 gem。

如前所述,通過將 gem 放置在 Gemfile 中,它將在 Rails 已載入。它首先需要來自引擎的 lib/blorgh.rb,然後 lib/blorgh/engine.rb,這是定義主要部分的檔案 發動機的功能。

為了使引擎的功能可以從應用程式中訪問,它 需要安裝在該應用程式的 config/routes.rb 檔案中:

mount Blorgh::Engine, at: "/blog"

此行將在應用程式中的 /blog 處安裝引擎。進行中 當應用程式使用 bin/rails 執行時,可以在http://localhost:3000/blog` 訪問 伺服器`。

其他引擎,例如 Devise,通過使 您在路由中指定自定義 helpers(例如 devise_for)。這些helpers 做完全相同的事情,在一個地方安裝引擎的功能部件 可定製的預定義路徑。

4.2 引擎設定

引擎包含 blorgh_articlesblorgh_comments 的 migrations 需要在應用程式的資料庫中建立的表,以便 引擎的 models 可以正確查詢它們。將這些 migrations 複製到 應用程式從應用程式的根目錄執行以下命令:

$ bin/rails blorgh:install:migrations

如果您有多個引擎需要複製 migrations,請使用 railties:install:migrations 代替:

$ bin/rails railties:install:migrations

此命令第一次執行時,會複製所有的 migrations 從發動機。下次執行時,它只會複製到 migrations 還沒複製過來。此命令的第一次執行將輸出 像這樣的東西:

Copied migration [timestamp_1]_create_blorgh_articles.blorgh.rb from blorgh
Copied migration [timestamp_2]_create_blorgh_comments.blorgh.rb from blorgh

第一個時間戳 ([timestamp_1]) 將是當前時間,第二個 時間戳 ([timestamp_2]) 將是當前時間加上一秒。原因 因為這是為了引擎的 migrations 在任何現有的之後執行 應用程式中的 migrations。

要在應用程式的上下文中執行這些 migrations,只需執行 bin/rails db:migrate. When accessing the engine through http://localhost:3000/blog, 文章將是空的。這是因為在應用程式內部建立的表是 與引擎中建立的不同。來吧,玩弄 新安裝的發動機。你會發現它和當它只是一個 引擎。

如果您只想從一個引擎執行 migrations,您可以通過 指定 SCOPE

$ bin/rails db:migrate SCOPE=blorgh

如果您想在刪除它之前恢復引擎的 migrations,這可能很有用。 要從 blorgh 引擎恢復所有 migrations,您可以執行以下程式碼:

$ bin/rails db:migrate SCOPE=blorgh VERSION=0

4.3 使用應用程式提供的類

4.3.1 使用應用程式提供的 Model

建立引擎時,它可能希望使用來自 應用程式提供引擎部件和部件之間的連結 應用程式。以blorgh引擎為例,發表文章和評論 有作者會很有意義。

一個典型的應用程式可能有一個 User 類,用於表示 文章或評論的作者。但可能有一種情況 應用程式將此類稱為不同的名稱,例如 Person。為了這 原因,引擎不應該專門為 User 硬編碼 associations 班級。

在這種情況下,為了簡單起見,應用程式將有一個名為 User 的類 代表應用程式的使用者(我們將開始製作這個 可進一步設定)。它可以在內部使用此命令產生 應用:

$ bin/rails generate model user name:string

此處需要執行 bin/rails db:migrate 命令以確保我們的 應用程式有 users 表供將來使用。

此外,為了簡單起見,文章表單將有一個名為的新文字欄位 author_name,使用者可以選擇放置他們的名字。然後發動機將 取這個名字並從中建立一個新的 User 物件,或者找到一個 已經有這個名字了。然後引擎會將文章與找到的或 建立了 User 物件。

首先,需要將 author_name 文字欄位新增到 app/views/blorgh/articles/_form.html.erb 部分在引擎內部。這可以 使用以下程式碼在 title 欄位上方新增:

<div class="field">
  <%= form.label :author_name %><br>
  <%= form.text_field :author_name %>
</div>

接下來,我們需要將 Blorgh::ArticlesController#article_params 方法更新為 允許新的表單引數:

def article_params
  params.require(:article).permit(:title, :text, :author_name)
end

Blorgh::Article model 然後應該有一些程式碼來轉換 author_name 將欄位轉換為實際的 User 物件並將其關聯為該文章的 author 在儲存文章之前。它還需要設定一個 attr_accessor 對於此欄位,以便為其定義 setter 和 getter 方法。

為此,您需要為 author_name 新增 attr_accessor,即 association 為作者和 before_validation 呼叫 app/models/blorgh/article.rbauthor association 將被硬編碼到 User 類暫時。

attr_accessor :author_name
belongs_to :author, class_name: "User"

before_validation :set_author

private
  def set_author
    self.author = User.find_or_create_by(name: author_name)
  end

通過使用 User 類表示 author association 的物件,連結 在引擎和應用程式之間建立。必須有辦法 將 blorgh_articles 表中的記錄與 users 表。因為 association 被稱為 author,所以應該有一個 author_id 列新增到 blorgh_articles 表中。

要產生這個新列,請在引擎中執行以下命令:

$ bin/rails generate migration add_author_id_to_blorgh_articles author_id:integer

由於 migration 的名稱和它後面的列規範,Rails 將自動知道您要向特定表中新增一列,並且 將其寫入 migration 中。你不需要多說 這。

此 migration 將需要在應用程式上執行。要做到這一點,它必須首先 使用以下命令複製:

$ bin/rails blorgh:install:migrations

請注意,此處僅複製了 one migration。這是因為第一 第一次執行此命令時複製了兩個 migrations。

NOTE Migration [timestamp]_create_blorgh_articles.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
NOTE Migration [timestamp]_create_blorgh_comments.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blorgh

使用以下命令執行 migration:

$ bin/rails db:migrate

現在所有的部分都準備好了,一個 action 將發生,它將關聯 作者 - 由 users 表中的記錄表示 - 有一篇文章, 由引擎中的 blorgh_articles 表表示。

最後,作者的名字應該顯示在文章的頁面上。新增此程式碼 在 app/views/blorgh/articles/show.html.erb 內的“標題”輸出上方:

<p>
  <b>Author:</b>
  <%= @article.author.name %>
</p>
4.3.2 使用應用程式提供的 Controller

因為 Rails controllers 通常共享用於身份驗證之類的程式碼 並訪問 session 變數,它們從 ApplicationController 繼承 預設。 Rails 引擎,但是範圍是獨立於主引擎執行的 應用程式,因此每個引擎都會獲得一個作用域 ApplicationController。這 namespace 防止程式碼衝突,但經常引擎 controllers 需要訪問 主應用程式的 ApplicationController 中的方法。一個簡單的方法 提供此訪問許可權是將引擎的作用域 ApplicationController 更改為 從主應用程式的 ApplicationController 繼承。對於我們的博格 引擎這將通過改變來完成 app/controllers/blorgh/application_controller.rb 看起來像:

module Blorgh
  class ApplicationController < ::ApplicationController
  end
end

預設情況下,引擎的 controllers 繼承自 Blorgh::ApplicationController。因此,在進行此更改後,他們將擁有 訪問主應用程式的 ApplicationController,就好像它們是 主要應用程式的一部分。

此更改確實需要從 Rails 應用程式執行引擎 有一個 ApplicationController

4.4 設定引擎

本節介紹如何使 User 類可設定,其次是 發動機的一般設定提示。

4.4.1 在應用中設定設定設定

下一步是在應用程式中製作代表一個User的類 為引擎定製。這是因為該類可能並不總是 User,如前所述。為了使此設定可定製,引擎 將有一個名為 author_class 的設定設定,用於 指定哪個類代表應用程式內的使用者。

要定義此設定設定,您應該在裡面使用 mattr_accessor 引擎的 Blorgh module。將此行新增到 lib/blorgh.rb 內 引擎:

mattr_accessor :author_class

此方法的工作原理與其兄弟 attr_accessorcattr_accessor 類似,但 在具有指定名稱的 module 上提供 setter 和 getter 方法。到 使用它,必須使用 Blorgh.author_class 來引用它。

下一步是將 Blorgh::Article model 切換到這個新設定。 改變這個 model 裡面的 belongs_to association (app/models/blorgh/article.rb) 到這個:

belongs_to :author, class_name: Blorgh.author_class

Blorgh::Article model 中的 set_author 方法也應該使用這個類:

self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)

為了節省必須一直在 author_class 結果上呼叫 constantize, 你可以改寫裡面的 author_class getter 方法 Blorgh module 在 lib/blorgh.rb 檔案中總是呼叫 constantize 在返回結果之前儲存了 value:

def self.author_class
  @@author_class.constantize
end

這會將上述 set_author 的程式碼轉換為:

self.author = Blorgh.author_class.find_or_create_by(name: author_name)

導致它的行為更短,更隱含。這 author_class 方法應該總是返回一個 Class 物件。

由於我們將 author_class 方法更改為返回 Class 而不是 String,我們還要修改我們在Blorgh::Article中的belongs_to定義 model:

belongs_to :author, class_name: Blorgh.author_class.to_s

要在應用程式中設定此設定設定,初始化程式應該 使用。通過使用初始化程式,設定將在 應用程式啟動並呼叫引擎的models,可能依賴於此 設定設定存在。

config/initializers/blorgh.rb 內部建立一個新的初始化程式 安裝了 blorgh 引擎的應用程式並將此內容放入其中:

Blorgh.author_class = "User"

這裡使用類的 String 版本非常重要, 而不是類本身。如果您要使用該類,Rails 會嘗試 載入該類,然後引用相關表。這可能導致 如果表尚不存在,則會出現問題。因此,一個 String 應該是 使用,然後在引擎中使用 constantize 轉換為類。

繼續嘗試建立一篇新文章。你會看到它在 和以前一樣,除了這次引擎使用設定 在 config/initializers/blorgh.rb 中設定以瞭解類是什麼。

現在沒有對類是什麼的嚴格依賴,只有 API 是什麼 類必須是。引擎只需要這個類來定義一個 find_or_create_by 方法返回該類的物件,要 建立時與文章相關聯。這個物件當然應該有 可以引用它的某種識別符號。

4.4.2 通用引擎設定

在引擎中,有時您可能希望使用諸如 初始化程式、國際化或其他設定選項。最棒的 新聞是這些事情是完全可能的,因為一個 Rails 引擎共享 與 Rails 應用程式的功能大致相同。其實一個Rails 應用程式的功能實際上是由 引擎!

如果您希望使用初始化程式 - 應該在引擎啟動之前執行的程式碼 載入 - 它的位置是 config/initializers 資料夾。這個目錄的 功能在 Initializers section 的設定指南,並且有效 與目錄中的 config/initializers 目錄完全相同 應用。如果您想使用標準初始化程式,情況也是如此。

對於語言環境,只需將語言環境檔案放在 config/locales 目錄中, 就像在應用程式中一樣。

5 測試引擎

產生引擎時,內部會建立一個較小的虛擬應用程式 它在 test/dummy。此應用程式用作發動機的安裝點, 使測試引擎變得極其簡單。您可以通過以下方式擴充套件此應用程式 從目錄中產生 controllers、models 或 views,然後使用 那些測試你的引擎。

test 目錄應該像典型的 Rails 測試環境一樣對待, 允許進行單元、功能和整合測試。

5.1 功能測試

編寫功能測試時值得考慮的一個問題是 測試將在應用程式上執行 - test/dummy 應用程式 - 而不是您的引擎。這是由於測試的設定 環境;引擎需要一個應用程式作為主機來測試其主要 功能,尤其是 controllers。這意味著如果你要製作一個 在 controller 的功能測試中,典型的 GET 到 controller 如下所示:

module Blorgh
  class FooControllerTest < ActionDispatch::IntegrationTest
    include Engine.routes.url_helpers

    def test_index
      get foos_url
      # ...
    end
  end
end

它可能無法正常工作。這是因為應用程式不知道如何 將這些請求路由到引擎,除非您明確告訴它如何。到 為此,您必須將 @routes 實例變數設定為引擎的路由集 在您的設定程式碼中:

module Blorgh
  class FooControllerTest < ActionDispatch::IntegrationTest
    include Engine.routes.url_helpers

    setup do
      @routes = Engine.routes
    end

    def test_index
      get foos_url
      # ...
    end
  end
end

這告訴應用程式您仍然要嚮應用程式執行 GET 請求 index action 這個controller,但是你想用引擎的路由來獲取 在那裡,而不是應用程式的那個。

這也確保引擎的 URL helpers 將在您的 測試。

6 改進引擎功能

本節說明如何在 主 Rails 應用程式。

6.1 覆蓋 Models 和 Controllers

引擎 models 和 controllers 可以被父應用程式重新開啟以擴充套件或裝飾它們。

覆蓋可以組織在專用目錄 app/overrides 中,該目錄預載入在 to_prepare callback 中。

zeitwerk 模式下,你會這樣做:

# 設定/應用程式.rb
module MyApp
  class Application < Rails::Application
    # ...

    overrides = "#{Rails.root}/app/overrides"
    Rails.autoloaders.main.ignore(overrides)
    config.to_prepare do
      Dir.glob("#{overrides}/**/*_override.rb").each do |override|
        load override
      end
    end
  end
end

classic 模式下:

# 設定/應用程式.rb
module MyApp
  class Application < Rails::Application
    # ...

    config.to_prepare do
      Dir.glob("#{Rails.root}/app/overrides/**/*_override.rb").each do |override|
        require_dependency override
      end
    end
  end
end
6.1.1 使用 class_eval 重新開啟現有類

例如,為了覆蓋引擎 model

# Blorgh/app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    has_many :comments

    def summary
      "#{title}"
    end
  end
end

您只需建立一個reopens該類的檔案:

# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
  def time_since_created
    Time.current - created_at
  end

  def summary
    "#{title} - #{truncate(text)}"
  end
end

覆蓋 reopens 類或 module 非常重要。如果它們不在記憶體中,則使用 classmodule keywords 將定義它們,這將是不正確的,因為定義存在於引擎中。使用如上所示的 class_eval 可確保您重新開啟。

6.1.2 使用 ActiveSupport::Concern 重新開啟現有類

使用 Class#class_eval 非常適合簡單的調整,但對於更復雜的 類修改,您可能需要考慮使用 ActiveSupport::Concern。 ActiveSupport::Concern 管理相互關聯的依賴 modules 和的載入順序 執行時的類允許您顯著模組化您的程式碼。

新增 Article#time_since_created覆蓋 Article#summary

# MyApp/app/models/blorgh/article.rb

class Blorgh::Article < ApplicationRecord
  include Blorgh::Concerns::Models::Article

  def time_since_created
    Time.current - created_at
  end

  def summary
    "#{title} - #{truncate(text)}"
  end
end
# Blorgh/app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    include Blorgh::Concerns::Models::Article
  end
end
# Blorgh/lib/concerns/models/article.rb

module Blorgh::Concerns::Models::Article
  extend ActiveSupport::Concern

  # 'included do' causes the included code to be evaluated in the
  # context where it is included (article.rb), rather than being
  # executed in the module's context (blorgh/concerns/models/article).
  included do
    attr_accessor :author_name
    belongs_to :author, class_name: "User"

    before_validation :set_author

    private
      def set_author
        self.author = User.find_or_create_by(name: author_name)
      end
  end

  def summary
    "#{title}"
  end

  module ClassMethods
    def some_class_method
      'some class method string'
    end
  end
end

6.2 自動載入和引擎

請檢查自動載入和重新載入常量 有關自動載入和引擎的更多資訊的指南。

6.3 覆蓋 Views

當 Rails 尋找一個 view 進行渲染時,它會首先在 app/views 中尋找 應用程式的目錄。如果在那裡找不到 view,它會簽入 具有此目錄的所有引擎的 app/views 目錄。

當應用程式被要求為 Blorgh::ArticlesController 渲染 view 時 index action,它會先尋找路徑 應用程式中的 app/views/blorgh/articles/index.html.erb。如果不能 找到它,它將檢視引擎內部。

您可以在應用程式中通過簡單地在以下位置建立一個新檔案來覆蓋此 view app/views/blorgh/articles/index.html.erb。然後你可以完全改變什麼 這個 view 通常會輸出。

現在通過在 app/views/blorgh/articles/index.html.erb 建立一個新檔案來嘗試這個 並將此內容放入其中:

<h1>Articles</h1>
<%= link_to "New Article", new_article_path %>
<% @articles.each do |article| %>
  <h2><%= article.title %></h2>
  <small>By <%= article.author %></small>
  <%= simple_format(article.text) %>
  <hr>
<% end %>

6.4 路線

預設情況下,引擎內的路由與應用程式隔離。這是 由 Engine 類中的 isolate_namespace 呼叫完成。這本質上 意味著應用程式及其引擎可以具有相同命名的路由和 他們不會發生衝突。

引擎內的路線繪製在 Engine 類上 config/routes.rb,像這樣:

Blorgh::Engine.routes.draw do
  resources :articles
end

通過擁有這樣的隔離路線,如果您希望連結到某個區域 應用程式中的引擎,您將需要使用引擎的路由 代理方式。呼叫正常的路由方法如 articles_path 可能會結束 如果應用程式和引擎都有這樣的 helper 已定義。

例如,以下示例將轉到應用程式的 articles_path 如果該模板是從應用程式渲染的,或者引擎的 articles_path 如果它是從引擎渲染的:

<%= link_to "Blog articles", articles_path %>

要使這條路線始終使用引擎的 articles_path 路由 helper 方法, 我們必須呼叫與路由代理方法同名的方法 引擎。

<%= link_to "Blog articles", blorgh.articles_path %>

如果您希望以類似方式引用引擎內部的應用程式,請使用 main_app helper:

<%= link_to "Home", main_app.root_path %>

如果你在引擎中使用它,它會總是轉到 應用程式的根。如果您不使用 main_app“路由代理” 方法呼叫,它可能會轉到引擎或應用程式的根目錄, 取決於它是從哪裡呼叫的。

如果從引擎內部呈現的模板嘗試使用 應用程式的路由 helper 方法,它可能會導致未定義的方法呼叫。 如果您遇到這樣的問題,請確保您沒有嘗試呼叫 不帶 main_app 字首的應用程式路由方法 引擎。

6.5 資產

引擎中的資產以與完整應用程式相同的方式工作。因為 引擎類繼承自 Rails::Engine,應用程式將知道 在引擎的 app/assetslib/assets 目錄中查詢資產。

與引擎的所有其他元件一樣,資產應該是名稱空間的。 這意味著如果你有一個名為 style.css 的資產,它應該放在 app/assets/stylesheets/[engine name]/style.css,而不是 app/assets/stylesheets/style.css。如果此資產沒有名稱空間,則有一個 主機應用程式可能具有相同命名的資產,在 在這種情況下,應用程式的資產優先,引擎的資產優先 將被忽略。

想象一下,你確實有一個資產位於 app/assets/stylesheets/blorgh/style.css。將此資產包含在 應用程式,只需使用 stylesheet_link_tag 並引用資產就好像它 在發動機內:

<%= stylesheet_link_tag "blorgh/style.css" %>

您還可以使用 Asset 將這些資產指定為其他資產的依賴項 管道處理檔案中的 require 語句:

/*
 *= require blorgh/style
 */

資訊。記住,為了使用像 Sass 或 CoffeeScript 這樣的語言,你 應該將相關庫新增到您引擎的 .gemspec

6.6 分離資產和預編譯

在某些情況下,引擎不需要您的引擎資產 主機應用程式。例如,假設您已經建立了一個管理功能 只存在於您的引擎。在這種情況下,宿主應用程式不會 需要要求 admin.cssadmin.js。只有 gem 的管理佈局需要 這些資產。主機應用程式包含沒有意義 "blorgh/admin.css" 在其樣式表中。在這種情況下,你應該 為預編譯明確定義這些資產。這告訴鏈輪新增 觸發 bin/rails assets:precompile 時的引擎資產。

您可以在 engine.rb 中定義用於預編譯的資產:

initializer "blorgh.assets.precompile" do |app|
  app.config.assets.precompile += %w( admin.js admin.css )
end

有關更多資訊,請閱讀 Asset Pipeline 指南

6.7 其他 Gem 依賴

引擎內的 Gem 依賴項應該在 .gemspec 檔案中指定 在引擎的根部。原因是發動機可能安裝為 寶石。如果要在 Gemfile 中指定依賴項,這些將不會 被傳統的 gem 安裝識別,所以它們不會被安裝, 導致發動機故障。

指定應在執行期間與引擎一起安裝的依賴項 傳統的 gem install,在 Gem::Specification 塊內指定 在引擎中的 .gemspec 檔案中:

s.add_dependency "moo"

指定僅應作為開發安裝的依賴項 應用程式的依賴項,像這樣指定它:

s.add_development_dependency "moo"

兩種依賴都會在裡面執行 bundle install 時安裝 的應用程式。將僅使用 gem 的開發依賴項 當發動機的開發和測試正在進行時。

請注意,如果您想在引擎啟動時立即要求依賴 需要,您應該在引擎初始化之前要求它們。為了 例子:

require "other_engine/engine"
require "yet_another_engine/engine"

module MyEngine
  class Engine < ::Rails::Engine
  end
end

7 載入和設定掛鉤

Rails 程式碼通常可以在載入應用程式時引用。 Rails 負責這些框架的載入順序,因此當您載入框架(例如 ActiveRecord::Base)時,您過早地違反了您的應用程式與 Rails 的隱式合同。此外,通過在應用程式啟動時載入諸如 ActiveRecord::Base 之類的程式碼,您正在載入整個框架,這可能會減慢啟動時間,並可能導致與應用程式的載入順序和啟動衝突。

載入和設定掛鉤是允許您在不違反與 Rails 的載入合同的情況下掛鉤到此初始化過程的 API。這也將緩解引導效能下降並避免衝突。

7.1 避免載入 Rails 框架

由於Ruby是動態語言,所以有些程式碼會導致載入不同的Rails框架。以這個片段為例:

ActiveRecord::Base.include(MyActiveRecordHelper)

這段程式碼意味著當這個檔案被載入時,它會遇到ActiveRecord::Base。這次遇到導致 Ruby 查詢該常量的定義並需要它。這會導致在啟動時載入整個 Active Record 框架。

ActiveSupport.on_load 是一種機制,可用於將程式碼的載入推遲到實際需要時。上面的程式碼片段可以更改為:

ActiveSupport.on_load(:active_record) do
  include MyActiveRecordHelper
end

當載入 ActiveRecord::Base 時,此新程式碼段將僅包含 MyActiveRecordHelper

7.2 什麼時候呼叫 Hooks?

在 Rails 框架中,當載入特定庫時會呼叫這些掛載機制。例如,當載入 ActionController::Base 時,會呼叫 :action_controller_base 掛載機制。這意味著所有帶有 :action_controller_base 掛載機制的 ActiveSupport.on_load 呼叫都將在 ActionController::Base 的上下文中被呼叫(這意味著 self 將是一個 ActionController::Base)。

7.3 修改程式碼使用Load Hooks

修改程式碼通常很簡單。如果您有一行程式碼引用了 Rails 框架,例如 ActiveRecord::Base,您可以將該程式碼包裝在載入掛鉤中。

修改對 include 的呼叫

ActiveRecord::Base.include(MyActiveRecordHelper)

變成

ActiveSupport.on_load(:active_record) do
  # self refers to ActiveRecord::Base here,
  # so we can call .include
  include MyActiveRecordHelper
end

修改對 prepend 的呼叫

ActionController::Base.prepend(MyActionControllerHelper)

變成

ActiveSupport.on_load(:action_controller_base) do
  # self refers to ActionController::Base here,
  # so we can call .prepend
  prepend MyActionControllerHelper
end

修改對類方法的呼叫

ActiveRecord::Base.include_root_in_json = true

變成

ActiveSupport.on_load(:active_record) do
  # self refers to ActiveRecord::Base here
  self.include_root_in_json = true
end

7.4 可用的負載掛鉤

這些是您可以在自己的程式碼中使用的載入掛鉤。要掛鉤以下類之一的初始化過程,請使用可用掛鉤。

班級
ActionCable action_cable
ActionCable::Channel::Base action_cable_channel
ActionCable::Connection::Base action_cable_connection
ActionCable::Connection::TestCase action_cable_connection_test_case
ActionController::API action_controller_api
ActionController::API action_controller
ActionController::Base action_controller_base
ActionController::Base action_controller
ActionController::TestCase action_controller_test_case
ActionDispatch::IntegrationTest action_dispatch_integration_test
ActionDispatch::Response action_dispatch_response
ActionDispatch::Request action_dispatch_request
ActionDispatch::SystemTestCase action_dispatch_system_test_case
ActionMailbox::Base action_mailbox
ActionMailbox::InboundEmail action_mailbox_inbound_email
ActionMailbox::Record action_mailbox_record
ActionMailbox::TestCase action_mailbox_test_case
ActionMailer::Base action_mailer
ActionMailer::TestCase action_mailer_test_case
ActionText::Content action_text_content
ActionText::Record action_text_record
ActionText::RichText action_text_rich_text
ActionView::Base action_view
ActionView::TestCase action_view_test_case
ActiveJob::Base active_job
ActiveJob::TestCase active_job_test_case
ActiveRecord::Base active_record
ActiveStorage::Attachment active_storage_attachment
ActiveStorage::VariantRecord active_storage_variant_record
ActiveStorage::Blob active_storage_blob
ActiveStorage::Record active_storage_record
ActiveSupport::TestCase active_support_test_case
i18n i18n

7.5 可用的設定掛載機制

設定掛鉤不掛鉤到任何特定框架,而是在整個應用程式的上下文中執行。

用例
before_configuration 要執行的第一個可設定塊。在執行任何初始化程式之前呼叫。
before_initialize 要執行的第二個可設定塊。在框架初始化之前呼叫。
before_eager_load 要執行的第三個可設定塊。如果 config.eager_load 設定為 false,則不執行。
after_initialize 要執行的最後一個可設定塊。在框架初始化後呼叫。

可以在 Engine 類中呼叫設定掛鉤。

module Blorgh
  class Engine < ::Rails::Engine
    config.before_configuration do
      puts 'I am called before any initializers'
    end
  end
end

回饋

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

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

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

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

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