隨專案演進,純 SQL 遷移腳本愈來愈冗長、難以組織。Rails 透過 ActiveRecord Migration 解決此問題。Sam Livingston-Gray 開發獨立 Ruby 工具 SQrbL (SQL + Ruby),結合 Ruby 表達力與 SQL,生成分層級的遷移腳本目錄結構。
include Sqrbl
Sqrbl.migration do
@output_directory = '/path/to/generated/sql'
group "Widgets" do
step "Create widgets" do
up do
helpers do
def widget_import_note
'Imported from old_widgets'
end
end
action "Migrate old_widgets" do
<<-SQL
#{ insert_into("new_widgets", {
:name => 'widget_name',
:part_num => 'CONCAT("X_", part_number)',
:note => widget_import_note
}) }
FROM old_widgets
SQL
end
end
down do
action "Drop imported organizational contacts" do
'DELETE FROM new_widgets WHERE note LIKE "Imported from old_widgets"'
end
end
end
end
group 'Second Group' do
step 'Step one' do
up { write '-- Step one up' }
down { write '-- Step one down' }
end
step 'Step two' do
up { write '-- Step two up' }
down { write '-- Step two down' }
end
end
end
/path/to/generated/sql/
├── up/
│ ├── 1_widgets/
│ │ └── 1_create_widgets.sql
│ └── 2_second_group/
│ ├── 1_step_one.sql
│ └── 2_step_two.sql
├── down/
│ ├── 1_widgets/
│ │ └── 1_create_widgets.sql
│ └── 2_second_group/
│ ├── 1_step_one.sql
│ └── 2_step_two.sql
├── all_up.sql
└── all_down.sql
-- Migrate old_widgets
INSERT INTO new_widgets (name, part_num, note)
SELECT widget_name AS name,
CONCAT("X_", part_number) AS part_num,
"Imported from old_widgets" AS note
FROM old_widgets;
-- Step one up
-- Step two up
INSERT(insert_into helper),缺乏完整 SQL DSL(CREATE TABLE、ALTER 等仍需手寫 SQL 字串)。版本 0.1.3 仍屬早期。SQrbL 展示「內建領域特定語言」如何降低 SQL 遷移腳本的維護複雜度。雖功能尚待擴充,但其「群組-步驟-上/下」結構、Helper 重用、自動生成執行順序檔案等設計,為遷移工具提供了優秀的參考架構。