Capistrano で名前空間内に変数を設定する。

Capistrano名前空間を使うと、同じ名前のタスクを複数定義できる。だが、setメソッドで設定する変数は、名前空間で装飾されない。setで設定された変数はCapfile内でグローバルになる。

namespace :aaa do
  set :path, "A" # 名前空間 aaa に入っている path に見える。
  desc "task aaa"
  task :x do
    puts path
  end
end

namespace :bbb do
  set :path, "B" # 名前空間 bbb に入っている path に見える。
  desc "task bbb"
  task :x do
    puts path
  end
end

これは以下のようになってしまう。ヒドイ。

$ cap aaa:x bbb:x
  * executing `aaa:x'
B
  * executing `bbb:x'
B

ブロックローカル変数を使うことならできる。ただし、遅延初期化ができない。

namespace :aaa do
  path = "A" # 普通に変数に設定する。
  desc "task aaa"
  task :x do
    puts path
  end
end

namespace :bbb do
  path = "B" # 普通に変数に設定する。
  desc "task bbb"
  task :x do
    puts path
  end
end

pathはnamespaceブロック内でローカルになっている。

$ cap aaa:x bbb:x
  * executing `aaa:x'
A
  * executing `bbb:x'
B

$ cap bbb:x aaa:x
  * executing `bbb:x'
B
  * executing `aaa:x'
A

以下のようにして、自分でset,fetchを名前空間で装飾すれば、遅延初期化できる。が、鬱陶しい。

def setv(symbol, namespace = nil, value = nil, &block)
  s = namespace ? :"#{namespace}_#{symbol}" : symbol
  if block_given?
    set(s, &block)
  else
    set(s, value)
  end
end

def v(symbol, namespace = nil, default = nil, &block)
  s = namespace ? :"#{namespace}_#{symbol}" : symbol
  if block_given?
    return fetch(s, &block)
  else
    return fetch(s, default)
  end
end

namespace :aaa do
  setv :path, name, "A" # 現在のnamespaceをnameで参照できる。
  desc "task aaa"
  task :x do
    puts v(:path, name)
  end
end

namespace :bbb do
  setv :path, name, "B" # 現在のnamespaceをnameで参照できる。
  desc "task bbb"
  task :x do
    puts v(:path, name)
  end
end

デフォルトのレシピを複数の名前空間で使いたかったが、あきらめた。:aaaと:bbbでCapfileを分けた方がいいと思う。