1 第一次接觸
當您使用 rails
指令建立應用程式時,您實際上是在使用 Rails 產生器。之後,您可以透過呼叫 bin/rails generate
來取得所有可用產生器的清單。
$ rails new myapp
$ cd myapp
$ bin/rails generate
若要建立 Rails 應用程式,我們會使用 rails
全域指令,它會使用透過 gem install rails
安裝的 Rails 版本。當位於應用程式的目錄中時,我們會使用 bin/rails
指令,它會使用與應用程式搭配的 Rails 版本。
您會取得附帶 Rails 的所有產生器的清單。若要查看特定產生器的詳細說明,請使用 --help
選項呼叫產生器。例如
$ bin/rails generate scaffold --help
2 建立您的第一個產生器
產生器建立在 Thor 之上,它提供用於剖析的強大選項,以及用於處理檔案的絕佳 API。
我們來建立一個產生器,它會在 config/initializers
內建立一個名為 initializer.rb
的初始化程式檔案。第一步是在 lib/generators/initializer_generator.rb
中建立一個檔案,其內容如下
class InitializerGenerator < Rails::Generators::Base
def create_initializer_file
create_file "config/initializers/initializer.rb", <<~RUBY
# Add initialization content here
RUBY
end
end
我們的全新產生器非常簡單:它繼承自 Rails::Generators::Base
,並有一個方法定義。當呼叫產生器時,產生器中的每個公開方法會按定義順序依序執行。我們的這個方法會呼叫 create_file
,它會在指定的目的地建立一個檔案,其內容為指定內容。
若要呼叫我們的全新產生器,我們會執行
$ bin/rails generate initializer
在繼續之前,我們先來看看我們的全新產生器的說明
$ bin/rails generate initializer --help
如果產生器具有命名空間,例如 ActiveRecord::Generators::ModelGenerator
,Rails 通常可以衍生出良好的說明,但此情況並非如此。我們可以用兩種方式解決這個問題。第一種方式是透過在我們的產生器內呼叫 desc
來新增說明
class InitializerGenerator < Rails::Generators::Base
desc "This generator creates an initializer file at config/initializers"
def create_initializer_file
create_file "config/initializers/initializer.rb", <<~RUBY
# Add initialization content here
RUBY
end
end
現在我們可以在全新產生器上呼叫 --help
來查看新的說明。
新增說明的第二種方式是在與我們的產生器相同的目錄中建立一個名為 USAGE
的檔案。我們將在下一步中執行此操作。
3 使用產生器建立產生器
產生器本身有一個產生器。讓我們移除我們的 InitializerGenerator
,並使用 bin/rails generate generator
來產生一個新的產生器
$ rm lib/generators/initializer_generator.rb
$ bin/rails generate generator initializer
create lib/generators/initializer
create lib/generators/initializer/initializer_generator.rb
create lib/generators/initializer/USAGE
create lib/generators/initializer/templates
invoke test_unit
create test/lib/generators/initializer_generator_test.rb
這是剛剛建立的產生器
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
end
首先,請注意產生器繼承自 Rails::Generators::NamedBase
,而不是 Rails::Generators::Base
。這表示我們的產生器至少需要一個引數,這個引數將是初始化程式的名稱,且我們的程式碼可透過 name
使用它。
我們可以透過檢查新產生器的說明來查看
$ bin/rails generate initializer --help
Usage:
bin/rails generate initializer NAME [options]
此外,請注意產生器有一個名為 source_root
的類別方法。此方法指向我們的範本位置(如果有的話)。預設它指向剛剛建立的 lib/generators/initializer/templates
目錄。
為了了解產生器範本如何運作,讓我們建立檔案 lib/generators/initializer/templates/initializer.rb
,內容如下
# Add initialization content here
讓我們變更產生器,在呼叫時複製這個範本
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
def copy_initializer_file
copy_file "initializer.rb", "config/initializers/#{file_name}.rb"
end
end
現在讓我們執行我們的產生器
$ bin/rails generate initializer core_extensions
create config/initializers/core_extensions.rb
$ cat config/initializers/core_extensions.rb
# Add initialization content here
我們看到 copy_file
使用我們的範本內容建立了 config/initializers/core_extensions.rb
。(目的地路徑中使用的 file_name
方法繼承自 Rails::Generators::NamedBase
。)
4 產生器命令列選項
產生器可以使用 class_option
來支援命令列選項。例如
class InitializerGenerator < Rails::Generators::NamedBase
class_option :scope, type: :string, default: "app"
end
現在我們的產生器可以使用 --scope
選項來呼叫
$ bin/rails generate initializer theme --scope dashboard
選項值可透過 options
在產生器方法中存取
def copy_initializer_file
@scope = options["scope"]
end
5 產生器解析
在解析產生器名稱時,Rails 會使用多個檔案名稱來尋找產生器。例如,當您執行 bin/rails generate initializer core_extensions
時,Rails 會嘗試依序載入下列各個檔案,直到找到一個
rails/generators/initializer/initializer_generator.rb
generators/initializer/initializer_generator.rb
rails/generators/initializer_generator.rb
generators/initializer_generator.rb
如果找不到這些檔案,系統會產生錯誤。
我們將產生器放在應用程式的 lib/
目錄中,因為該目錄位於 $LOAD_PATH
中,因此 Rails 可以找到並載入檔案。
6 覆寫 Rails 產生器範本
Rails 在解析產生器範本檔案時也會在多個位置尋找。其中一個位置是應用程式的 lib/templates/
目錄。此行為允許我們覆寫 Rails 內建產生器所使用的範本。例如,我們可以覆寫 scaffold 控制器範本 或 scaffold 檢視範本。
為了實際操作,我們來建立一個 lib/templates/erb/scaffold/index.html.erb.tt
檔案,其內容如下
<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>
請注意,範本是一個 ERB 範本,用於呈現另一個 ERB 範本。因此,任何應該出現在結果範本中的 <%
都必須在產生器範本中轉譯為 <%%
。
現在,讓我們執行 Rails 內建的 scaffold 產生器
$ bin/rails generate scaffold Post title:string
...
create app/views/posts/index.html.erb
...
app/views/posts/index.html.erb
的內容是
<% @posts.count %> Posts
7 覆寫 Rails 產生器
Rails 內建的產生器可透過 config.generators
進行設定,包括完全覆寫某些產生器。
首先,讓我們仔細瞭解 scaffold 產生器的運作方式。
$ bin/rails generate scaffold User name:string
invoke active_record
create db/migrate/20230518000000_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
create app/views/users/_user.html.erb
invoke resource_route
invoke test_unit
create test/controllers/users_controller_test.rb
create test/system/users_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
從輸出中,我們可以看到腳手架產生器呼叫其他產生器,例如 scaffold_controller
產生器。其中一些產生器也會呼叫其他產生器。特別是,scaffold_controller
產生器會呼叫其他幾個產生器,包括 helper
產生器。
讓我們使用新的產生器覆寫內建的 helper
產生器。我們將產生器命名為 my_helper
$ bin/rails generate generator rails/my_helper
create lib/generators/rails/my_helper
create lib/generators/rails/my_helper/my_helper_generator.rb
create lib/generators/rails/my_helper/USAGE
create lib/generators/rails/my_helper/templates
invoke test_unit
create test/lib/generators/rails/my_helper_generator_test.rb
在 lib/generators/rails/my_helper/my_helper_generator.rb
中,我們將產生器定義為
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<~RUBY
module #{class_name}Helper
# I'm helping!
end
RUBY
end
end
最後,我們需要告訴 Rails 使用 my_helper
產生器,而不是內建的 helper
產生器。我們使用 config.generators
來執行此操作。在 config/application.rb
中,我們新增
config.generators do |g|
g.helper :my_helper
end
現在,如果我們再次執行腳手架產生器,我們會在動作中看到 my_helper
產生器
$ bin/rails generate scaffold Article body:text
...
invoke scaffold_controller
...
invoke my_helper
create app/helpers/articles_helper.rb
...
您可能會注意到內建 helper
產生器的輸出包含「呼叫 test_unit」,而 my_helper
的輸出則沒有。儘管 helper
產生器預設不會產生測試,但它確實提供了使用 hook_for
執行的掛鉤。我們可以在 MyHelperGenerator
類別中包含 hook_for :test_framework, as: :helper
來執行相同操作。請參閱 hook_for
文件以取得更多資訊。
7.1 產生器備援
覆寫特定產生器的另一種方法是使用備援。備援允許產生器命名空間委派給另一個產生器命名空間。
例如,假設我們要使用自己的 my_test_unit:model
產生器覆寫 test_unit:model
產生器,但我們不想要取代所有其他 test_unit:*
產生器,例如 test_unit:controller
。
首先,我們在 lib/generators/my_test_unit/model/model_generator.rb
中建立 my_test_unit:model
產生器
module MyTestUnit
class ModelGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
def do_different_stuff
say "Doing different stuff..."
end
end
end
接著,我們使用 config.generators
將 test_framework
產生器設定為 my_test_unit
,但我們也設定一個備用選項,讓任何遺失的 my_test_unit:*
產生器解析為 test_unit:*
config.generators do |g|
g.test_framework :my_test_unit, fixture: false
g.fallbacks[:my_test_unit] = :test_unit
end
現在,當我們執行 scaffold 產生器時,我們會看到 my_test_unit
已取代 test_unit
,但只有模型測試受到影響
$ bin/rails generate scaffold Comment body:text
invoke active_record
create db/migrate/20230518000000_create_comments.rb
create app/models/comment.rb
invoke my_test_unit
Doing different stuff...
invoke resource_route
route resources :comments
invoke scaffold_controller
create app/controllers/comments_controller.rb
invoke erb
create app/views/comments
create app/views/comments/index.html.erb
create app/views/comments/edit.html.erb
create app/views/comments/show.html.erb
create app/views/comments/new.html.erb
create app/views/comments/_form.html.erb
create app/views/comments/_comment.html.erb
invoke resource_route
invoke my_test_unit
create test/controllers/comments_controller_test.rb
create test/system/comments_test.rb
invoke helper
create app/helpers/comments_helper.rb
invoke my_test_unit
invoke jbuilder
create app/views/comments/index.json.jbuilder
create app/views/comments/show.json.jbuilder
8 個應用程式範本
應用程式範本是一種特殊的產生器。它們可以使用所有 產生器輔助方法,但寫成 Ruby 腳本,而不是 Ruby 類別。以下是一個範例
# template.rb
if yes?("Would you like to install Devise?")
gem "devise"
devise_model = ask("What would you like the user model to be called?", default: "User")
end
after_bundle do
if devise_model
generate "devise:install"
generate "devise", devise_model
rails_command "db:migrate"
end
git add: ".", commit: %(-m 'Initial commit')
end
首先,範本會詢問使用者是否要安裝 Devise。如果使用者回答「是」(或「y」),範本會將 Devise 加入 Gemfile
,並詢問使用者 Devise 使用者模型的名稱 (預設為 User
)。稍後,在執行 bundle install
之後,範本會執行 Devise 產生器和 rails db:migrate
(如果指定了 Devise 模型)。最後,範本會對整個應用程式目錄執行 git add
和 git commit
。
我們可以在產生新的 Rails 應用程式時,透過將 -m
選項傳遞給 rails new
指令來執行我們的範本
$ rails new my_cool_app -m path/to/template.rb
或者,我們可以使用 bin/rails app:template
在現有的應用程式中執行我們的範本
$ bin/rails app:template LOCATION=path/to/template.rb
範本也不需要儲存在本機 — 你可以指定 URL,而不是路徑
$ rails new my_cool_app -m http://example.com/template.rb
$ bin/rails app:template LOCATION=http://example.com/template.rb
9 個產生器輔助方法
Thor 透過 Thor::Actions
提供許多產生器輔助方法,例如
除了這些,Rails 也透過 Rails::Generators::Actions
提供許多輔助方法,例如
回饋
我們鼓勵您協助提升本指南的品質。
如果您發現任何錯字或事實錯誤,請協助我們修正。若要開始,您可以閱讀我們的 文件貢獻 章節。
您也可能會發現不完整的內容或未更新的內容。請務必為主要內容新增任何遺漏的文件。請務必先查看 Edge Guides,以驗證問題是否已在主要分支中修正。查看 Ruby on Rails 指南準則 以了解風格和慣例。
如果您發現需要修正的地方,但無法自行修補,請 開啟問題。
最後但並非最不重要的一點,我們非常歡迎在 官方 Ruby on Rails 論壇 上針對 Ruby on Rails 文件進行任何討論。