Rails應用的助手:Rake背后的故事
作為一個Rails的開發(fā)者,你可能很熟悉使用Rake進行你的測試,或者使用Rake db:migrate運行你的migrations,但是你真的知道Rake的背后故事嗎?你意識到可以自己寫一個Rake任務或者一個有用的lib嗎?
51CTO推薦專題:Ruby On Rails開發(fā)教程
下面是我們使用Rake任務的例子:
1、給列表中的用戶發(fā)送郵件
2、每晚數(shù)據(jù)的計算和報告
3、過期或重新生成緩存
4、備份數(shù)據(jù)和svn版本
5、運行數(shù)據(jù)處理腳本
一、歷史回顧:Make
為了了解Rake的來歷,我們先了解一下Rake的爺爺:Make。讓我們回到那個代碼塊需要編譯,解釋性語言和iphone還沒出現(xiàn)在地球上的時代?;氐侥菚r,我們下載的大型程序,還是一堆源代碼和一個shell腳本。這個shell腳本包含了所有需要用來compile/link/build的代碼。你需要運行“install_me.sh”這個腳本,每一行代碼將被運行(編譯每一行源文件),然后生成一個你能夠運行的文件。
對于大多數(shù)人這樣是不錯的,但是對于程序開發(fā)人員卻是一個不幸。每次你對源代碼進行一個小的改動,并進行測試的時候,你需要回到shell腳本,并重新編譯所有的源代碼,顯然對于大的程序“那是相當?shù)?rdquo;耗時的。
1977年貝爾實驗室的Stuart Feldman創(chuàng)造了“make”。解決了編譯時間過長的問題。Make用來編譯程序,取得兩方面的進步:
(1)Make可以發(fā)現(xiàn)哪個文件在上一次編譯后改動過,根據(jù)這點,再次運行Make時,僅編譯改動過的文件。這個很大程序上減少了重新編譯大型程序的時間。
(2)Make可以進行從屬跟蹤。你可以告訴編譯器,源文件A的編譯需要源文件B,源文件B的編譯需要源文件C,所以Make在編譯A時發(fā)現(xiàn)B沒有編譯,將會先編譯B。
可以這樣定義:Make是一個可執(zhí)行程序。像ls或dir一樣。讓Make理解如何讓編譯一個項目,需要創(chuàng)建一個makefile文件,描述所有的源文件和依賴關(guān)系。makefiles有自己的語法,你不用去了解。
這些年Make出現(xiàn)了其他的變體,并且被其他的語言使用。事實上,Ruby用戶在Rake出現(xiàn)前也在使用它。但是,Ruby并不需要編譯,我們用它來干嘛?Ruby是一個解釋性語言,我們不需要編譯它的源代碼,所以Ruby程序員為什么使用它呢?兩個重要的原因:
(1)創(chuàng)建任務
在大型的應用中,你經(jīng)常編寫腳本,在命令行下運行一些任務。比如清除緩存,維護任務,或者遷移數(shù)據(jù)庫。你可以寫一個MakeFile來組織你的任務,而不是寫十個不相干的腳本(或者一個復雜的)。這樣你可以簡單的運行:“make stupid”。
(2)從屬任務跟蹤
當你開始寫一些維護任務的時候,可能發(fā)現(xiàn)有些任務的使用可能有重復。比如,“migrate”任務和“schema:dump”都需要鏈接數(shù)據(jù)庫,這樣我可以創(chuàng)建一個任務"connect_to_database",使“migrate”和“schema:dump”都依賴于"connect_to_database",這樣下次運行“migrate”時,"connect_to_database"會先于“migrate”運行。
#p#
二、如何得到Rake
幾年前,Jim Weirich在一個java項目上使用了Make,他發(fā)現(xiàn)如果在他的Makefile中寫一小段Ruby代碼將會帶來非常大的方便。所以他創(chuàng)建了Rake。
Jim為Rake創(chuàng)建了任務功能,附屬關(guān)系跟蹤,甚至創(chuàng)建了時間段判斷(timestamp recognition),(在上一次編譯的基礎(chǔ)上僅編譯改動的部分),當然,對于Ruby,我們并不需要編譯。我很想知道Jim在代碼里做了什么,你也想知道吧。Jim可能從來沒想給這個代碼寫個文檔,可能現(xiàn)在他也是被煩透了,寫了一個。
三、Rake如何工作
開始我想給這個部分起名為"How to get wasted with Rake"。那么我想喝點酒,該怎么做呢?
1、去買酒
2、喝酒
3、喝醉
如果我要使用Rake完成這個任務,我會創(chuàng)建一個“Rakefile”文件:
- 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
這樣我可以在這個Rakefile的目錄,分別運行這些任務:
- $ Rake purchaseAlcohol
- Purchased Vodka
- $ Rake mixDrink
- Mixed Fuzzy Navel
- $ Rake getSmashed
- Dood, everthing's blurry, can I halff noth'r drinnnk?
酷!但是從順序上看,我可以用任何的順序運行這個任務。比如喝醉在買酒或者喝酒之前。當然這不符合人的習慣。
四、Rake的順序
- task :purchaseAlcohol do
- puts "Purchased Vodka"
- end
- task :mixDrink => :purchaseAlcohol do
- puts "Mixed Fuzzy Navel"endtask :getSmashed => :mixDrink do
- puts "Dood, everthing's blurry, can I halff noth'r drinnnk?"
- end
這樣,如果想喝酒,就得先去買,如果想喝醉,就得先喝酒。
- $ 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?
看到了吧,我喝醉和,因為酒已經(jīng)買了,也被我喝了?,F(xiàn)在,你的欲望無法滿足了,你想讓你的朋友加入進來。就像一個團隊的開發(fā),如果你想加入一個新人,你得有合適的規(guī)劃。你得有文檔。那么問題來了。
#p#
五、如何給我的Rake添加文檔
Rake添加文檔非常的方便,使用“desc”就可以了:
- 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任務的時候,你需要一個好方法將他們分類,這時用到了命名空間,如果我在上面的例子使用了命名空間,那么:
- 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創(chuàng)建幾個文件夾:
- 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,或者加入其他的部分。
八、如何為我的Rails應用寫一個Rake任務
一個Rails應用中,已經(jīng)有了一些Rake任務,你可以在你的項目目錄里運行:Rake --tasks。為了給你的Rails應用添加一個新的任務,你可以打開/lib/tasks目錄(已經(jīng)存在的),添加一個叫something.Rake的文件,這個任務會被自動的檢索到,這些任務會被添加到Rake tasks列表中,你可以在根目錄里運行他們,現(xiàn)在把我們上面的例子放到這個Rails應用中。
- utils.Rake
- 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} 來得到Rails應用的當前位置,現(xiàn)在運行“Rake --tasks”,你可以看到我們的任務已經(jīng)添加到tasks列表中了。
- ...
- 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
- ...
#p#
九、如何在任務中調(diào)用Rails的model
呵呵,這個正是我最多使用Rake的地方,寫一個Rake任務,代替原來需要手工操作的地方,或者一些任務代替經(jīng)常需要按照計劃自動執(zhí)行(使用 cronjobs)的事情。就像我開頭說的那樣我用Rake任務執(zhí)行下面的擦作:
1、給列表中的用戶發(fā)送郵件
2、每晚數(shù)據(jù)的計算和報告
3、過期或重新生成緩存
4、備份數(shù)據(jù)和svn版本
5、運行數(shù)據(jù)處理腳本
這個補充了原來的功能,而且相當簡單。下面這個任務是檢查用戶的過期時間,對快過期的用戶發(fā)送郵件。
- utils.Rake
- 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"。
- task(:send_expire_soon_emails => :environment) do
如果在我的開發(fā)環(huán)境上運行這個任務,我只需要"Rake utils:send_expire_soon_emails",如果我想在產(chǎn)品環(huán)境下運行這個任務,我需要"Rake Rails_ENV=production utils:send_expire_soon_emails"。
原文作者:Jason Seifer
文章來源:http://jasonseifer.com/2010/04/06/rake-tutorial
【編輯推薦】