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

建立和自定義 Rails 產生器和模板

如果您打算改進工作流程,Rails 產生器是必不可少的工具。通過本指南,您將學習如何建立產生器和自定義現有產生器。

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

1 第一次接觸

當您使用 rails 命令建立應用程式時,您實際上是在使用 Rails 產生器。之後,您可以通過呼叫 bin/rails generate 來獲取所有可用產生器的列表:

$ rails new myapp
$ cd myapp
$ bin/rails generate

要建立 rails 應用程式,我們使用 rails 全域性命令,即通過 gem install rails 安裝的 rails gem。當在您的應用程式目錄中時,我們使用命令 bin/rails,它使用此應用程式中捆綁的 rails。

您將獲得 Rails 附帶的所有產生器的列表。例如,如果您需要 helper 產生器的詳細說明,您可以簡單地執行以下操作:

$ bin/rails generate helper --help

2 建立您的第一個產生器

從 Rails 3.0 開始,產生器建立在 Thor 之上。 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", "# Add initialization content here"
  end
end

create_fileThor::Actions 提供的方法。 create_file 和其他 Thor 方法的文件可以在 Thor 的文件 中找到

我們的新產生器非常簡單:它繼承自 Rails::Generators::Base 並且有一個方法定義。呼叫產生器時,產生器中的每個公共方法都按照定義的順序依次執行。最後,我們呼叫 create_file 方法,該方法將在給定的目的地建立一個具有給定內容的檔案。如果您熟悉 Rails 應用程式模板 API,您會對新的產生器 API 感到賓至如歸。

要呼叫我們的新產生器,我們只需要執行以下操作:

$ bin/rails generate initializer

在我們繼續之前,讓我們看看我們全新的產生器描述:

$ bin/rails generate initializer --help

如果產生器具有名稱空間,則 Rails 通常能夠產生良好的描述,如 ActiveRecord::Generators::ModelGenerator,但在這種特殊情況下則不然。我們可以通過兩種方式解決這個問題。第一個是在我們的產生器中呼叫 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", "# Add initialization content here"
  end
end

現在我們可以通過在新產生器上呼叫 --help 來檢視新描述。新增描述的第二種方法是在與產生器相同的目錄中建立一個名為 USAGE 的檔案。我們將在下一步中做到這一點。

3 使用產生器建立產生器

產生器本身有一個產生器:

$ 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

# 這裡新增初始化內容

現在讓我們更改產生器以在呼叫時複製此模板:

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

我們可以看到,現在在 config/initializers/core_extensions.rb 上建立了一個名為 core_extensions 的初始化程式,其中包含我們模板的內容。這意味著 copy_file 將源根目錄中的檔案複製到我們提供的目標路徑中。當我們從 Rails::Generators::NamedBase 繼承時,會自動建立方法 file_name

本指南的 最後一節 介紹了可用於產生器的方法。

4 產生器查詢

當你執行 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

5 自定義您的工作流程

Rails 自己的產生器足夠靈活,可以讓您自定義 scaffolding。它們可以在 config/application.rb 中設定,這些是一些預設值:

config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :test_unit, fixture: true
end

在我們自定義我們的工作流之前,讓我們先看看我們的 scaffold 是什麼樣子的:

$ bin/rails generate scaffold User name:string
      invoke  active_record
      create    db/migrate/20130924151154_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
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      invoke  test_unit
      create    test/application_system_test_case.rb
      create    test/system/users_test.rb

檢視此輸出,很容易理解產生器在 Rails 3.0 及更高版本中的工作方式。 scaffold 產生器實際上並不產生任何東西,它只是呼叫其他人來完成工作。這允許我們新增/替換/刪除任何這些呼叫。例如,scaffold 產生器呼叫 scaffold_controller 產生器,後者呼叫 erb、test_unit 和 helper 產生器。由於每個產生器都有一個單一的職責,它們很容易重用,避免了程式碼重複。

工作流的下一個自定義將是完全停止為 scaffolds 產生樣式表和測試夾具檔案。我們可以通過將我們的設定更改為以下內容來實現這一點:

config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :test_unit, fixture: false
end

如果我們使用 scaffold 產生器產生另一個資源,我們可以看到不再建立樣式表、JavaScript 和夾具檔案。如果您想進一步自定義它,例如使用 DataMapper 和 RSpec 而不是 Active Record 和 TestUnit,只需將它們的 gem 新增到您的應用程式並設定您的產生器。

為了演示這一點,我們將建立一個新的 helper 產生器,它只是新增一些實例變數讀取器。首先,我們在 rails 名稱空間中建立一個產生器,因為這是 rails 搜尋用作掛載機制的產生器的地方:

$ 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

之後,我們可以同時刪除templates目錄和source_root 從我們的新產生器呼叫類方法,因為我們不需要它們。 新增下面的方法,所以我們的產生器看起來像下面這樣:

# 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", <<-FILE
module #{class_name}Helper
  attr_reader :#{plural_name}, :#{plural_name.singularize}
end
    FILE
  end
end

我們可以通過為產品建立 helper 來試用我們的新產生器:

$ bin/rails generate my_helper products
      create  app/helpers/products_helper.rb

它將在 app/helpers 中產生以下 helper 檔案:

module ProductsHelper
  attr_reader :products, :product
end

這正是我們所期望的。我們現在可以通過再次編輯 config/application.rb 來告訴 scaffold 使用我們新的 helper 產生器:

config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :test_unit, fixture: false
  g.stylesheets     false
  g.helper          :my_helper
end

並在呼叫產生器時在 action 中看到它:

$ bin/rails generate scaffold Article body:text
      [...]
      invoke    my_helper
      create      app/helpers/articles_helper.rb

我們可以在輸出中注意到我們的新助手被呼叫而不是 Rails 預設值。但是缺少一件事,那就是對我們新產生器的測試,為此,我們將重用舊的 helpers 測試產生器。

從 Rails 3.0 開始,由於掛載機制概念,這很容易做到。我們新的 helper 不需要專注於一個特定的測試框架,它可以簡單地提供一個掛載機制,一個測試框架只需要實現這個掛載機制就可以相容。

為此,我們可以通過以下方式更改產生器:

# 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", <<-FILE
module #{class_name}Helper
  attr_reader :#{plural_name}, :#{plural_name.singularize}
end
    FILE
  end

  hook_for :test_framework
end

現在,當呼叫 helper 產生器並將 TestUnit 設定為測試框架時,它將嘗試同時呼叫 Rails::TestUnitGeneratorTestUnit::MyHelperGenerator。由於這些都沒有被定義,我們可以告訴我們的產生器呼叫 TestUnit::Generators::HelperGenerator,因為它是一個 Rails 產生器。為此,我們只需要新增:

# 搜尋 :helper 而不是 :my_helper
hook_for :test_framework, as: :helper

現在您可以為另一個資源重新執行 scaffold 並檢視它產生的測試!

6 通過更改產生器模板自定義您的工作流程

在上面的步驟中,我們只是想在產生的 helper 中新增一行,而不新增任何額外的功能。有一種更簡單的方法可以做到這一點,它是通過替換現有產生器的模板,在這種情況下為 Rails::Generators::HelperGenerator

在 Rails 3.0 及更高版本中,產生器不僅會在源根目錄中查詢模板,還會在其他路徑中搜索模板。其中之一是 lib/templates。由於我們想要自定義 Rails::Generators::HelperGenerator,我們可以通過簡單地在 lib/templates/rails/helper 中建立一個名為 helper.rb 的模板副本來實現。因此,讓我們使用以下內容建立該檔案:

module <%= class_name %>Helper
  attr_reader :<%= plural_name %>, :<%= plural_name.singularize %>
end

並恢復 config/application.rb 中的最後一次更改:

config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :test_unit, fixture: false
end

如果您產生另一個資源,您可以看到我們得到了完全相同的結果!如果您想通過在 lib/templates/erb/scaffold 內建立 edit.html.erbindex.html.erb 等來自定義 scaffold 模板和/或佈局,這很有用。

Rails 中的 Scaffold 模板經常使用 ERB 標籤;這些標籤需要 轉義,以便產生的輸出是有效的 ERB 程式碼。

例如,模板中將需要以下轉義的 ERB 標籤 (注意額外的 %)...

<%%= stylesheet_link_tag :application %>

...產生以下輸出:

<%= stylesheet_link_tag :application %>

7 新增產生器回退

關於產生器的最後一個對外掛產生器非常有用的特性是回退。例如,假設您想在 TestUnit 之上新增一個功能,就像 shouda 所做的那樣。由於 TestUnit 已經實現了 Rails 所需的所有產生器,而 shoulda 只是想覆蓋其中的一部分,因此 shoulda 沒有必要再次重新實現一些產生器,它可以簡單地告訴 Rails 使用 TestUnit 產生器,如果在 Shoulda 名稱空間下沒有找到。

我們可以通過再次更改 config/application.rb 輕鬆模擬這種行為:

config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :shoulda, fixture: false

  # Add a fallback!
  g.fallbacks[:shoulda] = :test_unit
end

現在,如果你建立一個 Comment scaffold,你會看到 shoulda 產生器被呼叫,最後,它們只是回退到 TestUnit 產生器:

$ bin/rails generate scaffold Comment body:text
      invoke  active_record
      create    db/migrate/20130924143118_create_comments.rb
      create    app/models/comment.rb
      invoke    shoulda
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml
      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
      invoke    shoulda
      create      test/controllers/comments_controller_test.rb
      invoke    my_helper
      create      app/helpers/comments_helper.rb
      invoke    jbuilder
      create      app/views/comments/index.json.jbuilder
      create      app/views/comments/show.json.jbuilder
      invoke  test_unit
      create    test/application_system_test_case.rb
      create    test/system/comments_test.rb

回退允許您的產生器承擔單一責任,增加程式碼重用並減少重複量。

8 申請模板

既然您已經瞭解瞭如何在應用程式內部使用產生器,您是否知道它們也可以用於 generate 應用程式?這種產生器被稱為“模板”。這是模板 API 的簡要介紹 view。有關詳細文件,請參閱 Rails 應用程式模板指南

gem "rspec-rails", group: "test"
gem "cucumber-rails", group: "test"

if yes?("Would you like to install Devise?")
  gem "devise"
  generate "devise:install"
  model_name = ask("What would you like the user model to be called? [user]")
  model_name = "user" if model_name.blank?
  generate "devise", model_name
end

在上面的模板中,我們指定應用程式依賴於 rspec-railscucumber-rails gem,因此這兩個將被新增到 Gemfile 中的 test 組中。然後我們向用戶提出一個關於他們是否願意安裝 Devise 的問題。如果使用者對這個問題回答“y”或“yes”,那麼模板將把 Devise 新增到任何組之外的 Gemfile 中,然後執行 ​​devise:install 產生器。然後這個模板接受使用者輸入並執行 devise 產生器,使用者的最後一個問題的答案被傳遞給這個產生器。

假設此模板位於名為 template.rb 的檔案中。我們可以通過使用 -m 選項並傳入檔名來使用它來修改 rails new 命令的結果:

$ rails new thud -m template.rb

此命令將產生 Thud 應用程式,然後將模板應用於產生的輸出。

模板不必儲存在本地系統上,-m 選項也支援線上模板:

$ rails new thud -m https://gist.github.com/radar/722911/raw/

雖然本指南的最後一部分沒有介紹如何產生人類已知的最棒的模板,但它將帶您瞭解可用的方法,以便您可以自己開發它。這些相同的方法也可用於產生器。

9 新增命令列引數

Rails 產生器可以輕鬆修改以接受自定義命令列引數。此功能來自 Thor

class_option :scope, type: :string, default: 'read_products'

現在我們的產生器可以被呼叫如下:

$ bin/rails generate initializer --scope write_products

命令列引數通過產生器類中的 options 方法訪問。例如:

@scope = options['scope']

10 產生器方法

以下是適用於 Rails 的產生器和模板的方法。

Thor 提供的方法不在本指南中,可以在 Thor 的文件 中找到

10.1 gem

指定應用程式的 gem 依賴項。

gem "rspec", group: "test", version: "2.1.0"
gem "devise", "1.1.5"

可用選項有:

  • :group - 該寶石所在的 Gemfile 組。
  • :version - 您要使用的 gem 的版本字串。也可以指定為方法的第二個引數。
  • :git - 此 gem 的 git 儲存庫的 URL。

傳遞給此方法的任何其他選項都放在行尾:

gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master"

上面的程式碼會將以下行放入 Gemfile 中:

gem "devise", git: "https://github.com/plataformatec/devise.git", branch: "master"

10.2 gem_group

將 gem 條目包裝在一個組中:

gem_group :development, :test do
  gem "rspec-rails"
end

10.3 add_source

將指定的源新增到 Gemfile

add_source "http://gems.github.com"

這個方法也需要一個塊:

add_source "http://gems.github.com" do
  gem "rspec-rails"
end

10.4 inject_into_file

將程式碼塊注入檔案中的定義位置。

inject_into_file 'name_of_file.rb', after: "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY'
  puts "Hello World"
RUBY
end

10.5 gsub_file

替換檔案中的文字。

gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'

可以使用正則表示式使這種方法更加精確。您也可以以相同的方式使用 append_fileprepend_file 將程式碼分別放置在檔案的開頭和結尾。

10.6 application

在應用程式類定義之後直接向 config/application.rb 新增一行。

application "config.asset_host = 'http://example.com'"

這個方法也可以帶一個塊:

application do
  "config.asset_host = 'http://example.com'"
end

可用選項有:

  • :env - 為這個設定選項指定一個環境。如果您希望將此選項與塊語法一起使用,推薦的語法如下:
application(nil, env: "development") do
  "config.asset_host = 'http://localhost:3000'"
end

10.7 git

執行指定的 git 命令:

git :init
git add: "."
git commit: "-m First commit!"
git add: "onefile.rb", rm: "badfile.cxx"

這裡雜湊的 values 是傳遞給特定 git 命令的引數或選項。根據此處顯示的最後一個示例,可以一次指定多個 git 命令,但不能保證它們的執行順序與指定它們的順序相同。

10.8 vendor

將檔案放入包含指定程式碼的 vendor 中。

vendor "sekrit.rb", '#top secret stuff'

這個方法也需要一個塊:

vendor "seeds.rb" do
  "puts 'in your app, seeding your database'"
end

10.9 lib

將檔案放入包含指定程式碼的 lib 中。

lib "special.rb", "p Rails.root"

這個方法也需要一個塊:

lib "super_special.rb" do
  "puts 'Super special!'"
end

10.10 rakefile

在應用程式的 lib/tasks 目錄中建立一個 Rake 檔案。

rakefile "test.rake", 'task(:hello) { puts "Hello, there" }'

這個方法也需要一個塊:

rakefile "test.rake" do
  %Q{
    task rock: :environment do
      puts "Rockin'"
    end
  }
end

10.11 initializer

在應用程式的 config/initializers 目錄中建立一個初始化程式:

initializer "begin.rb", "puts 'this is the beginning'"

這個方法也需要一個塊,期望返回一個字串:

initializer "begin.rb" do
  "puts 'this is the beginning'"
end

10.12 generate

執行指定的產生器,其中第一個引數是產生器名稱,其餘引數直接傳遞給產生器。

generate "scaffold", "forums title:string description:text"

10.13 rake

執行指定的 Rake 任務。

rake "db:migrate"

可用選項有:

  • :env - 指定執行此 rake 任務的環境。
  • :sudo - 是否使用 sudo 執行此任務。預設為 false

10.14 route

config/routes.rb 檔案新增文字:

route "resources :people"

10.15 readme

在模板的 source_path 中輸出檔案的內容,通常是 README。

readme "README"

回饋

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

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

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

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

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