Rake 簡介

作者:gregg
翻譯:andy

作爲一個Rails開發者,你可能熟練得運用“rake”運行你的測試,或者你利用“rake db:migrate”運行你的數據遷移任務(migration)。但是,你是否真正明白在那些Rake任務的背後發生了什麼嗎?你是否意識到,你可以 編寫屬於你的任務,或者創建好用的屬於你的Rake庫文件?

下面是我運用Rake任務的一些例子:

  • 創建一個成員列表,並給他們發送電子郵件。
  • 進行每天的數據計算和報告。
  • 清除過期和重新創建緩存。
  • 數據庫備份和subversion repository。
  • 運行任何類型的數據操作腳本。
  • 爲即將開始我們的談話倒酒(玩笑)

在這篇文章中,我們將理解爲什麼Rake被髮明,以及它如何協助我們的Rails應用程序。最終你將能夠編寫屬於你的任務。

內容列表

  1. 回顧:爲什麼是make?
  2. 如何獲取Rake?
  3. Rake如何工作?
  4. 如何表達rake依賴?
  5. 如何爲Rake任務寫文檔?
  6. Rake命名空間
  7. 如何寫有用的Ruby任務?
  8. 如何爲我的Rails應用寫Rake任務?
  9. 可以在任務中訪問Rails模型(Model)嗎?
  10. 哪裏可以找到更多的例子?

回顧:爲什麼是make?

爲了理解我們爲什麼擁有Rake,我們首先需要注目一下Rake的祖父Make。
和我回憶一下過去,在解釋性語言和iPhones誕生之前,每一段代碼都需要編譯。

那時候如果你下載大型程序,它們總是伴隨着大量源代碼和一個shell 腳本。 這個腳本包含你的計算機爲了編譯/鏈接/創建應用程序,需要的每一行代碼。你必須運行“install_me.sh”(shell腳本),每一行代碼將運 行(編譯每一個源文件),然後編譯出可執行文件。

對大多數人來說,此過程工作正常,除非你是開發程序的所有人中極其不幸運的一個。每一次你對源代碼做了很小改動,並且想測試它,你必須重新運行shell腳本來重新編譯所有代碼。顯然,如果是大型程序這將消耗大量時間。

  1. Make 識別那些文件/資源在最後一次編譯被改動。利用這個信息,Make在第二次執行的時候,只編譯被改動過的源文件。這動態地減少了重新編譯大型程序的時間。
  2. Make也包含依賴跟蹤(dependency tracking),所以你可以告訴編譯器,源文件A需要源文件B來正確編譯,源文件B依賴源文件C來正確編譯。因此,如果Make編譯源文件A而源文件B尚未編譯,那麼,系統將首先編譯源文件B。

也可以這樣解釋,“Make”是一個簡單的可執行程序,像“dir”或“ls”一樣。爲了讓Make如何知道編譯一個程序,必須創建一個“makefile”文件,它描述所有的源文件和依賴關係。

經過多年的發展,其它編程語言甚至開始運用Make。事實上,許多Ruby編程人員在rake出來之前一直運用它。
“但是,Ruby不需要被編譯,所以爲什麼Ruby程序員使用它?”你如此叫道。

是的,Ruby是一個解釋性的語言,我們不需要編譯我們的代碼,那麼爲什麼Ruby程序員使用Make文件呢?

好吧,有兩個主要原因:

  1. 創建任務—在每一個大型程序最後,你都要編寫命令行下可以運行的腳本。或許你想清除緩存,運行一個維護任務, 或者遷移數據庫。除吧創建10個單獨的shell腳本(或者一個大型的複雜腳本),你可以創建單個“Makefile”文件,在其中你可以通過任務組織所 有的東西。然後任務可以通過鍵入“make stupid”來運行。
  2. 依賴任務跟蹤—當你開始編寫維護任務的庫的時候,你開始注意到某些任務可能部分重複。例如,任務 “migrate”和任務“schema:dump”都需要連接一個數據庫。我可以創建叫“connect_to_database”一個任務,讓任務 “migrate”和任務“schema:dump”都依賴於“connect_to_database”下次我運行“migrate”,在 “migrate”運行之前“connect_to_database”將被運行。

如何獲取Rake?

不少年以前Jim Weirich 工作在一個使用Make的Java項目。當使用他的Makefile,他意識到,在他的Makefile中,如果他能夠編寫小的Ruby代碼片段,事情會 變得異常方便。所以他創造了rake。上個月在Railsconf會議,我們幸運的見到了Jim,他的確是個不錯的人。

Jim加入了創建任務功能,進行任務依賴跟蹤,甚至加入同樣時間戳識別。(只構建最後一次編譯被改動的源文件)。顯然,最後一個特性不是我們在Ruby中經常用到的。因爲我們不需要編譯。
我一直驚奇“Jim Weirich”所做的一切,現在你也知道了!Jim從來沒有特意的編寫Make,我猜想他只是不得以寫出來的。

Rake如何工作?

起初,我想給這部分加個“如何因Rake變得揮霍”的標題,但是,不是很直覺得那麼做。

假設我想喝醉,需要那幾個步驟?

  1. 買酒
  2. 喝酒
  3. 喝醉

如果我想運用Rake來調用以上的任務,可以創建一個包含以下內容的“Rakefile”文件:

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
task :mixDrink do
puts "Mixed Fuzzy Navel"
end
 
task :getSmashed do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

然後我在rake文件的同一個目錄下運行每一個任務,有一點如下所示:

$ rake purchaseAlcohol
Purchased Vodka
 
$ rake mixDrink
Mixed Fuzzy Navel
 
$ rake getSmashed
Dood, everthing's blurry, can I halff noth'r drinnnk?

相當酷!不管怎樣,從依賴的立場,我可以任何順序運行任務。雖然有時候,我希望在“mixDrink”或者“purchaseAlcohol”之前,我可以“getSmashed”,這不是簡單人力所能及。

如何表達rake依賴?

1
2
3
4
5
6
7
8
9
10
11
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

所以,現在我說“爲了mixDrink,我必須先purchaseAlcohol”,“爲了getSmashed 我必須mixDrink”。也許如你希望,依賴堆疊,因此:

$ rake purchaseAlcohol
Purchased Vodka
 
$ rake mixDrink
Purchased Vodka
Mixed Fuzzy Navel
 
$ rake getSmashed
Purchased Vodka
Mixed Fuzzy Navel
Dood, everthing's blurry, can I halff noth'r drinnnk?

正如你所看到的,現在當我將要“getSmashed”,依賴的任務“purchaseAlcohol”和“mixDrink”被調用。

經過不久,你可能被誘惑不斷膨脹你的酒癮,因此而擴充你的Rakefile文件。你也許會是你的朋友染上酒癮。正像一個真正的軟件項目,當你的團隊增加成員的時候,你需要保證你有良好的文檔。問題成了:

如何爲rake任務寫文檔?

Rake提供了一個簡潔的方法來爲task編寫文檔,下面是用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
desc "This task will purchase your Vodka"
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
desc "This task will mix a good cocktail"
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
desc "This task will drink one too many"
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end

正如你所看到的,每一個任務有一個“desc”。這允許我們和朋友鍵入“rake -T”或者“rake –tasks”

$rake --tasks
rake getSmashed # This task will drink one too many
rake mixDrink # This task will mix a good cocktail
rake purchaseAlcohol # This task will purchase your Vodka

很簡單吧?

Rake命名空間

一旦你成爲一個酒徒,並且使用大量的Rake任務,你可能需要一個好一點的辦法對他們加以分類。這是爲什麼使用命名空間的原因。如果我在上面的例子使用命名空間,它看起來將是這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace :alcoholic do
desc "This task will purchase your Vodka"
task :purchaseAlcohol do
puts "Purchased Vodka"
end
 
desc "This task will mix a good cocktail"
task :mixDrink => :purchaseAlcohol do
puts "Mixed Fuzzy Navel"
end
 
desc "This task will drink one too many"
task :getSmashed => :mixDrink do
puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
end
end

命名空間允許你根據分類爲任務分組,是的,你可以在一個Rakefile中擁有多個命名空間。現在,如果我執行“rake –tasks”,我將看到:

rake alcoholic:getSmashed        # This task will drink one too many
rake alcoholic:mixDrink # This task will mix a good cocktail
rake alcoholic:purchaseAlcohol # This task will purchase your Vodka

所以,現在運行以上任務,顯然你將運行“rake alcoholic:getSmashed”

如何寫有用的Ruby任務?

好了,你儘管寫ruby代碼就可以了。不是開玩笑。最近,我需要一個創建幾個目錄的腳本,所以我最終編寫了一個Rake任務,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
desc "Create blank directories if they don't already exist"
task(:create_directories) do
 
# The folders I need to create
shared_folders = ["icons","images","groups"]
 
for folder in shared_folders
 
# Check to see if it exists
if File.exists?(folder)
puts "#{folder} exists"
else
puts "#{folder} doesn't exist so we're creating"
Dir.mkdir "#{folder}"
end
 
end
end

默認情況下,rake可以訪問File Utils裏的任何東西,但是你可以包含任何額外的Ruby中的任何東西。

如何爲我的Rails應用寫Rake任務?

Rails應用程序附帶着許多預先存在的rake任務,你可以進入應用程序目錄下鍵入“rake –tasks”來顯示這些任務。如果你還沒有嘗試,馬上嘗試,我會等待….

爲你的Rails應用創建新的rake任務,你需要打開/lib/tasks目錄(目錄必須已經存在)。在這個目錄中,如果你創建屬於你的 Rakefile,並且命名爲“something.rake”,任務將自動被接受。任務將被增加到應用程序rake任務的列表中,你可以從應用程序的根 目錄運行他們。讓我們吧上面的例子,應用到我們的rails應用程序。

utils.tasks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace :utils do
desc "Create blank directories if they don't already exist"
task(:create_directories) do
 
# The folders I need to create
shared_folders = ["icons","images","groups"]
 
for folder in shared_folders
 
# Check to see if it exists
if File.exists?("#{RAILS_ROOT}/public/#{folder}")
puts "#{RAILS_ROOT}/public/#{folder} exists"
else
puts "#{RAILS_ROOT}/public/#{folder} doesn't exist so we're creating"
Dir.mkdir "#{RAILS_ROOT}/public/#{folder}"
end
 
end
end
end

注意,在代碼片斷中,我是如何使用#{RAILS_ROOT}來得到全路徑的。如果我現在我應用程序的根目錄下,運行“rake –tasks”,我將看到這個新功能將和其它rails rake任務混合在一起。

1
2
3
4
5
6
...
rake tmp:pids:clear # Clears all files in tmp/pids
rake tmp:sessions:clear # Clears all files in tmp/sessions
rake tmp:sockets:clear # Clears all files in tmp/sockets
rake utils:create_directories # Create blank directories if they don't already exist
...

很酷!現在,此處它變得比較有用。

可以在任務中訪問Rails模型(Model)嗎?

當然!事實上這正是我們使用rake的主要用途:編寫偶爾需要手動運行的任務,或者定時調度以自動運行的任務(使用cronjobs。正如我在文章開始說的,我使用Rake任務來做下面的事情:

  • 創建一個成員列表,並給他們發送電子郵件。
  • 進行每天的數據計算和報告。
  • 清除過期和重新創建緩存。
  • 數據庫備份和subversion repository。
  • 運行任何類型的數據操作腳本。

非常有用,而且簡單。下面是一個查找訂購關係即將過期用戶、併發送電子郵件的的rake任務。

utils.tasks

1
2
3
4
5
6
7
8
9
10
namespace :utils do
desc "Finds soon to expire subscriptions and emails users"
task(:send_expire_soon_emails => :environment) do
# Find users to email
for user in User.members_soon_to_expire
puts "Emailing #{user.name}"
UserNotifier.deliver_expire_soon_notification(user)
end
end
end

正如你看到的,只有一個步驟訪問你的模型(model),“=> :environment”:

  1. task(:send_expire_soon_emails => :environment) do

在你的開發數據庫上,運行這個任務,我將運行“rake utils:send_expire_soon_emails”。如果我想在產品數據庫上運行,我將運行“rake RAILS_ENV=production utils:send_expire_soon_emails”。

如果我想每天午夜,在產品數據庫上運行此任務,我將編寫一個如下cronjob:

0 0 * * * cd /var/www/apps/rails_app/ 
&& /usr/local/bin/rake RAILS_ENV=production utils:send_expire_soon_emails

相當簡便!

哪裏可以找到更多的例子?

既然你已經知道如何開始編寫rake任務,我想我應該給你一些資源。改善你編程最好的方法是閱讀他人的代碼,一些人們已經些好的rake任務。

如果你找到了其它比較好的,發貼子給我們。

追加

作爲追加,我幾分鐘以前受到Jim的電子郵件,解釋我如何可以簡化我的目錄創建腳本,相當酷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# This is needed because the existing version of 
# directory in Rake is slightly broken, but Jim says
# it'll be fixed in the next version.
alias :original_directory :directory
def directory(dir)
original_directory dir
Rake::Task[dir]
end
 
# Do the directory creation
namespace :utils do
task :create_directories => [
directory('public/icons'),
directory('public/images'),
directory('public/groups'),
]
end
發佈了10 篇原創文章 · 獲贊 0 · 訪問量 1985
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章