常在終端機裡下 rake db:migrate 指令,這個 rake 是什麼,後面那個 db:migrate 又是怎麼回事?
在開發 Ruby on Rails 專案的過程中,一定都看過或用過 rake db:migrate
這個指令,大部份的教學資料可能只會跟你說「只要照著打這個指令就行了」,沒有太多詳細的介紹。
其實這個 rake
指令,是一位已過世但我非常景仰的大師 Jim Weirich 所開發的。如果各位曾經聽說過 Make 這個工具,Rake 就像是 Ruby 版的 Make,你可以用 Ruby 語法來編寫 makefile。
動手練習
隨便建一個目錄來練習一下:
$ cd /tmp
$ mkdir rake_demo
$ cd rake_demo
$ rake
rake aborted!
No Rakefile found (looking for: rakefile, Rakefile, rakefile.rb, Rakefile.rb)
/Users/user/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `eval'
/Users/user/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `<main>'
(See full trace by running task with --trace)'
執行 rake
指令後,它會預期在這個目錄有 rakefile
, Rakefile
, rakefile.rb
, Rakefile.rb
這四個檔案任何一個檔案,但因為什麼都沒有所以出現上面的錯誤訊息。既然沒有,就做一個給它:
$ touch Rakefile
$ rake
rake aborted!
Don't know how to build task 'default' (see --tasks)
/Users/user/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `eval'
/Users/user/.rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `<main>'
(See full trace by running task with --trace)'
咦? 有 Rakefile
但還是有錯誤訊息,那是因為我們什麼都還沒寫,所以先打開 Rakefile
來寫一個簡單的寄信任務:
desc "寄發會員通知信"
task :send_email do
# ... 寄發信件功能實作
puts "Email Sent!"
end
task default: [:send_email]
task
方法可以定義任務的名字,後面接的 block 就是它實作的內容(實作功能就留給大家了,這裡僅先使用 puts 方法把結果印出來)。最上面的 desc
則僅是這個任務的描述,如果有描述的話,在 rake -T
指令列出任務清單的時候就看得到說明了:
$ rake -T
rake send_email # 寄發會員通知信
因為我在上面定義了一個叫做 send_email
的任務,如果要執行它的話,就是這樣下指令:
$ rake send_email
Email Sent!
又,因為這邊我在最後一行把預設的任務設定成 send_email
,所以即使我只輸入 rake
指令,它也會執行 rake send_email
任務。
另外,如果任務沒有在 rake -T
的清單上不表示任務不存在,它可能只是 desc 沒寫而已,但一樣是可以執行的喔。
那 rake db:migrate 中間的冒號?
了解 rake 的基本操作後,回來看看 Rails 裡常用的那個 rake db:migrate
是怎麼做的:
desc "寄發會員通知信"
task :send_email do
# ... 寄發信件功能實作
puts "Email Sent!"
end
namespace :db do
desc "Migrate the database"
task :migrate do
puts "migrating database!"
end
end
task default: [:send_email]
使用 namespace
方法,然後把任務包進去,這樣一來,任務的名字就會長得像這樣:
$ rake -T
rake db:migrate # Migrate the database
rake send_email # 寄發會員通知信
而且可以正常執行:
$ rake db:migrate
migrating database!
大概就是這樣囉! 關於更多 Rake 的使用方式,請參閱 Rake 的 Github 網站說明 https://github.com/ruby/rake
Rails 裡也是這樣嗎?
隨便開一個 Rails 專案,在根目錄應該可以看到一個 Rakefile,它的內容長這樣:
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks
咦? 怎麼都空空的? 讓我們直接挖 Rails 的原始檔出來看看(以 Rails 5.0.0 beta 4 版本為例):
# 檔案:railties/lib/rails/tasks.rb
require 'rake'
# Load Rails Rakefile extensions
%w(
annotations
dev
framework
initializers
log
middleware
misc
restart
routes
tmp
).tap { |arr|
arr << 'statistics' if Rake.application.current_scope.empty?
}.each do |task|
load "rails/tasks/#{task}.rake"
end
這段程式碼的意思就是一口氣載入一堆放在 railties/lib/rails/tasks/
目錄的一些檔案(.rake 檔),這些都是 Rails 預設會載入的任務。而那個 rake db:migrate
任務內容就藏在(這裡)。
要在 Rails 專案加上自己的 rake 指令?
在剛剛看到根目錄的 Rakefile 的最後一行寫到:
Rails.application.load_tasks
再繼續練習挖一下原始碼,可以翻到這個檔案,這個檔案大概 700 行左右,我只列出相關的程式碼。
# 檔案:railties/lib/rails/engine.rb
def load_tasks(app=self)
require "rake"
run_tasks_blocks(app)
self
end
def run_tasks_blocks(*)
super
paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end
看得出來 Rails 除了載入內建的任務外,其中這個 load_tasks
方法會把在專案裡 lib/tasks
目錄裡的任務檔也都一併讀進來。
除了挖原始碼看得出來之外,其實在 Rails 專案根目錄的 Rakefile 一打開的那兩行說明:
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
如果你想要加自己的 task 的話,就是寫在專案的 lib/tasks/
目錄裡。寫法跟上面寫 Rakefile 沒什麼兩樣,更多細節可參閱 Rails Guide 上的 Command Line 的 Custom Rake Tasks 章節。
Rails 5 的變化
提醒一下,在 Rails 5 之後,原本那些 rake 指令,例如:
$ rake db:migrate
$ rake routes
也都搬一份到 rails
指令底下囉,像是這樣:
$ rails db:migrate
$ rails routes
下次看到這樣的指令,不要以為是打錯字囉 :)
工商服務
實體課程:Ruby on Rails 實戰課程
線上課程:五倍學院線上課程