なんか共通の処理があって、自然な発想としてメソッドを定義してしまうことがあると思う。
以下の様な rake task 宣言を見て欲しい。例なのでDRYじゃないとか言わないでね。
namespace :report do
def report #1
# hogehoge
end
task :show do
p report
end
task :mail do
mail(body: report)
end
end
namespace :report2 do
def report #2
# fugafuga
end
task :show do
p report
end
task :mail do
mail(body: report)
end
end
ここで、 report という名前のメソッドを2回定義しているが、 namespace が分かれてるから大丈夫だって、思うだろう。
namespace 分かれてない。
分かれてません。どっちも main(Object) に宣言されてしまう。
rake report:show
ってすると report #2 のほうが呼ばれて fugafuga されてしまうのだ。
仕方ないので module に入れるべ、ってんで、若い頃の僕はこんなことをした。
namespace :report do
module ReportTask
def report #1
# hogehoge
end
end
task :show do
include ReportTask
p report
end
task :mail do
include ReportTask
mail(body: report)
end
end
namespace :report2 do
module ReportTask2
def report #2
# fugafuga
end
end
task :show do
include ReportTask2
p report
end
task :mail do
include ReportTask2
mail(body: report)
end
end
これで望みのメソッドを確実に呼べる。めでたしめでたし。
でもなかった。
include しちゃ意味ない
よく考えればわかることだが、 include ReportTask が何しているかってえと結局 main(Object) に include しちゃうのである。
幸い今回の例を普通に rake タスクとして使うぶんには問題は発生しないが、
task :report_all => ['report:mail', 'report2:mail']
などという横断的依存関係を持つタスクが現れた時点で破綻してしまう。
やはりここは
全体を module に
入れちまうのが安全というものだ。
module ReportTask
extend Rake::DSL
extend self
namespace :report do
def report #1
# hogehoge
end
task :show do
p report
end
task :mail do
mail(body: report)
end
end
end
module ReportTask2
extend Rake::DSL
extend self
namespace :report2 do
def report #2
# fugafuga
end
task :show do
p report
end
task :mail do
mail(body: report)
end
end
end
こうするとばっちりモジュールが分かれて安全安心になる。 extend Rake::DSL
は task
等のキーワードをモジュール内でも使用するためのもの、extend self
は def self.report
と本来書くべきところを手抜きするおまじないである。
どうせ他の場所から呼ばれるものではないし、干渉しない名前を考えるのも面倒なのでいっそ無名モジュールでも良い。
Module.new do
extend Rake::DSL
extend self
namespace :report
9/11 追記 無名モジュール定義時は全ての定数に self::
が必要。 http://qiita.com/kuboon/items/dc5f30abee136abcd319
しかし、この仕事、 namespace
がそもそもやってくれてもいいよねー?って思えてならないのでちょっと黒魔法チックな解決も一応ご用意した。
namespace を上書き定義してしまえ
require 'rake'
# これが元 https://github.com/ruby/rake/blob/v10.4.2/lib/rake/task_manager.rb#L205
module Rake
module TaskManager
def in_namespace(name, &block)
name ||= generate_name
@scope = Scope.new(name, @scope)
ns = NameSpace.new(self, @scope)
Module.new do
self.extend Rake::DSL
instance_eval(&block)
end
ns
ensure
@scope = @scope.tail
end
end
end
Admin::Application.load_tasks
これで、各 namespace は個別の Module 内で eval されるので全て期待通りに動く (多分)。
namespace の nest とかは全く考慮していないし、同じ namespace でもセクションが別だと名前空間が分かれてしまうみたいな問題があるけど、まあ大体のケースで動くし、もし nest等 が必要になったら include 使って回避するといいと思う。