本篇聚焦「讀者如何在自己的專案導入 Rails generator」,不再解釋 Mongory 本身
筆者以 Mongory 的 mongory:install
與 mongory:matcher
兩個 generator 作為範本,完整拆解檔案結構、模板寫法(含 ERB 空白陷阱)、initializer 內容注入與常見誤區,讓讀者能在任何 Rails 專案中快速打造一套可維護、可擴充且可重複使用的 generator。
——
rails g your_namespace:install
與 rails g your_namespace:xxx
的 generator。.erb
模板並避免多餘空白與重複內容注入。——
在任何 Rails gem 或應用中,generator 的慣例路徑如下:
lib/
generators/
your_namespace/
install/
install_generator.rb
templates/
initializer.rb.erb
thing/
thing_generator.rb
templates/
thing.rb.erb
核心類別命名規則:
YourNamespace::Generators::InstallGenerator < Rails::Generators::Base
YourNamespace::Generators::ThingGenerator < Rails::Generators::NamedBase
差別在於 NamedBase
會幫讀者處理 name
參數(例如 rails g your_namespace:thing class_in
)。
只要你的 Gem 檔案結構符合 Rails generator 慣例,就自動可以連結到指令。
——
以 Mongory 實作為範本,Install generator 目標是產生 config/initializers/xxx.rb
,並可依「已安裝的 gem」自動調整模板內容。
關鍵觀念:
template 'xxx.erb', 'target/path'
。ERB 內可寫條件分支,但要注意空白控制(下一節詳述)。參考骨架(節錄自 Mongory):
module YourNamespace
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path('templates', __dir__)
def create_initializer_file
@use_ar = gem_used?('activerecord')
@use_mongoid = gem_used?('mongoid')
@use_sequel = gem_used?('sequel')
template 'initializer.rb.erb', 'config/initializers/your_namespace.rb'
end
private
def gem_used?(gem_name)
Bundler.locked_gems.dependencies.key?(gem_name)
end
end
end
end
模板 initializer.rb.erb
建議做到:
@use_mongoid
時才註冊 Mongoid::Criteria
或 BSON::ObjectId
的轉換。——
很多生成器的模板在插入條件段落後,會多出空白行或多餘縮排,造成產生檔案髒亂、diff 不穩。Mongory 的 initializer.rb.erb
採用 ERB 的裁切語法抑制空白:
重點規則:
<% if cond -%>
與 <% end -%>
:
-%>
,會去除換行,使產生程式碼乾淨。<%= value -%>
:輸出值後去除該標記右邊的換行與空白。-%>
放在行尾,避免產出空白行。對照示例:
<% if @use_mongoid -%>
mc.register(Mongoid::Criteria)
mc.data_converter.configure do |dc|
dc.register(Mongoid::Document, :as_document)
dc.register(BSON::ObjectId, :to_s)
end
<% end -%>
若拿掉 -%>
,相同條件在不同環境下會殘留空白行,導致不必要的 diff。裁切語法能保證輸出緊湊、穩定。
——
NamedBase
類型的 generator 適合用來生成可命名的類別與對應測試,並更新既有設定檔。Mongory 的 指令mongory:matcher
做了三件事:
name
產生兩個檔案(類別與規格測試)。install
生成。require
行插入到 initializer(避免重複並維持排序/群組)。核心步驟(抽象化示例):
class ThingGenerator < Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
def create_files
@class_name = "#{class_name}Thing"
@file_name = file_name
template 'thing.rb.erb', "lib/your_namespace/things/#{@file_name}_thing.rb"
template 'thing_spec.rb.erb', "spec/your_namespace/things/#{@file_name}_thing_spec.rb"
end
def update_initializer
initializer_path = 'config/initializers/your_namespace.rb'
require_line = "require \"#\{Rails.root\}/lib/your_namespace/things/#{@file_name}_thing\""
YourNamespace::Generators::InstallGenerator.start unless File.exist?(initializer_path)
content = File.read(initializer_path)
return if content.include?(require_line) # idempotent
required_lines = content.scan(/.*require\s+[\"'].*_thing[\"'].*/)
anchor = required_lines.empty? ? "# frozen_string_literal: true" : required_lines.last
inject_into_file initializer_path, "\n#{require_line}\n", after: "#{anchor}\n"
end
end
這樣的做法有幾個工程優點:
——
以下列出 Mongory initializer 中幾個設計點,供讀者在自家專案借鏡:
mc.enable_symbol_snippets!
mc.register(Array)
mc.register(ActiveRecord::Relation) # 若專案使用 AR
mc.register(Mongoid::Criteria) # 若專案使用 Mongoid
mc.register(Sequel::Dataset) # 若專案使用 Sequel
mc.data_converter.configure do |dc|
dc.register(ActiveRecord::Base, :attributes)
# dc.register(Mongoid::Document, :as_document)
# dc.register(BSON::ObjectId, :to_s)
# dc.register(Sequel::Model) { values.transform_keys(&:to_s) }
end
mc.condition_converter.configure do |cc|
cc.key_converter.configure do |kc|
# kc.register(MyKeyObject, :to_string_key_pair)
end
cc.value_converter.configure do |vc|
# vc.register(MyWrapperType) { unwrap_value }
end
end
筆者建議把「範例註冊」與「專案實際註冊」以註解與段落清楚區隔,讀者將來維護時較不易混淆。
——
content.include?(line)
。-%>
。對策:條件塊與輸出使用裁切語法,並檢視產出檔。——
YourNamespace
。initializer.rb.erb
,確保在不同 ORM 組合下輸出穩定(以 -%>
控制空白)。require
,且不重複。rails g your_namespace:install
rails g your_namespace:thing awesome
看見 initializer 被建立且 require
正確插入,並能跑起測試,即完成驗收。
——
讀者可以直接把本文的骨架與策略搬到任何 Rails gem 或應用中:
如需更完整參考,可對照 Mongory 專案中的:
lib/generators/mongory/install/install_generator.rb
lib/generators/mongory/matcher/matcher_generator.rb
lib/generators/mongory/install/templates/initializer.rb.erb
這些範本可以一鍵套用到讀者的命名空間與領域模型中。
——
結語:Rails generator 的價值在於「讓讀者的專案成為可複製的工程產品」。以 Mongory 為範本,讀者能快速打造穩定、可重入、可擴充的 generator 系統,持續把日常操作沉澱為一鍵自動化。