問題描述
在rails項目中,有時候需要導出數據、處理歷史數據等,這時用rake是很方便的。
在rake中,有時候由於邏輯比較複雜,所以我們就會分離邏輯或者需要重用代碼,這時候可能會定義方法。
但是,在rake中定義方法有一個問題:在不同rake文件和不同命名空間下定義的方法,如果存在了同名方法,那麼後來定義的方法會覆蓋掉之前定義的方法,這時候可能會導致bug。
下面我們用一個例子來具體說明下:
比如有個項目 sample_app,我們有2個rake文件:
sample_app/lib/tasks/export_data/export_users.rake
sample_app/lib/tasks/handle_old_data/hanlde_old_users.rake
在項目中排列順序和這裏列出的順序一致。
對應代碼如下:
# sample_app/lib/tasks/export_data/export_users.rake
namespace :export_data
task export_users: :environment do
user_data = get_user_data
# other operations
end
def get_user_data
puts "In export_data, get_user_data"
# return user_data
end
end
#sample_app/lib/tasks/handle_old_data/hanlde_old_users.rake
namespace :handle_old_data
task handle_old_users: :environment do
user_data = get_user_data
# other operations
end
def get_user_data
puts "In handle_old_data, get_user_data"
# return user_data
end
end
在命令行執行 rake handle_old_data:handle_old_users時會打印出:
In handle_old_data, get_user_data
這時如果在命令行執行 rake export_data:export_users ,那麼會打印出來什麼呢?
答案是:
In handle_old_data, get_user_data
也就是說,在第一個rake中調用 get_user_data 方法時,實際上是調用的 第二個rake中定義的該方法。
那麼這是爲什麼呢?
問題解答和解決方案
這個問題出現的原因,其實仔細思考一下是不難得到答案的:
lib/tasks/ 目錄下的文件是依次加載的,而且rake中的命名空間(namespace)是隻對rake任務起作用的,不會對文件中的方法起作用,所以後加載的第二個rake文件中的方法定義覆蓋掉了前一個同名方法。
那麼,怎麼解決這個問題呢?
也許有人會說,不要用同名方法不就行了嗎?
這樣當然是一種解決問題的方式。但是這樣對於開發者來說很不友好–誰會喜歡在定義方法前,去全局搜索下別人有沒有用過這個方法名?
所以,我們需要更加優雅的解決方案。
我們可以有一種方式來很好地解決這個問題:
我們在rake中定義方法時,給方法加上命名空間–可以新定義一個class或者一個module,然後將方法定義到其中;這樣相當於將我們的方法定義限制在了我們期望的範圍內。
以同時使用module和class爲例,可以這樣解決問題:
# sample_app/lib/tasks/export_data/export_users.rake
namespace :export_data
task export_users: :environment do
user_data = ExportData::ExportUsers.get_user_data
# other operations
end
module ExportData
class ExportUsers
def self.get_user_data
puts "In export_data, get_user_data"
# return user_data
end
end
end
end
#sample_app/lib/tasks/handle_old_data/hanlde_old_users.rake
namespace :handle_old_data
task handle_old_users: :environment do
user_data = HandleOldData::HandleOldUsers.get_user_data
# other operations
end
module HandleOldData
class HandleOldUsers
def self.get_user_data
puts "In handle_old_data, get_user_data"
# return user_data
end
end
end
end
這樣,這兩個方法就相互不影響。
在命令行執行 rake handle_old_data:handle_old_users 時會打印出:
In handle_old_data, get_user_data
這時如果在命令行執行 rake export_data:export_users ,那麼會打印出來:
In export_data, get_user_data