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