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.js
和application.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
目錄裡面是標準的assets
、controllers
、helpers
、
您應該熟悉的 jobs
、mailers
、models
和 views
目錄
從應用程式。當我們編寫引擎時,我們將在以後的部分中更多地研究 models。
在 app/assets
目錄中,有 images
和
stylesheets
目錄,您應該再次熟悉,因為它們
應用程式的相似性。然而,這裡的一個差別是,每個
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/helpers
、app/jobs
、app/mailers
和 app/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.rb
和 test/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_articles
和 blorgh_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.rb
。 author
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_accessor
和 cattr_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 非常重要。如果它們不在記憶體中,則使用 class
或 module
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/assets
和 lib/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.css
或 admin.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 討論區。