2018-02-06

Rakeタスクにおけるnamespace内でのメソッド定義

Rakeタスク内でメソッド定義するとObjectのprivateメソッドとして定義されます。なので下のような書き方は一見namespace配下にメソッドを定義しているように見えますがObjectのメソッドとして定義されてしまっています。

def outside_method
  puts 'outside method'
end

namespace :foo do
  def inside_method
    puts 'inside method'
  end

  desc "bar"
  task :bar do
    outside_method # "outside_method"
    inside_method # "inside_method"
    puts method(:outside_method).owner # => Object
    puts method(:inside_method).owner # => Object
  end
end

Rakeは各タスクの定義ファイルをKernel.loadしていて、namespaceはインスタンス変数の@scopeに名前空間を設定しつつ、与えられたブロックを普通にyieldしています↓

module Rake
  module TaskManager
# ...
    def in_namespace(name)
      name ||= generate_name
      @scope = Scope.new(name, @scope)
      ns = NameSpace.new(self, @scope)
      yield(ns)
      ns
    ensure
      @scope = @scope.tail
    end

なので、block内のメソッド定義はnamespace外のメソッド定義と同じです。

同様にCapistranoもRake::Applicationクラスを継承して作られているので、namespace内で記述されたメソッドはObjectのメソッドとして定義されます。

ちなみにrspecのdescribeのブロックは動的に作成されるクラスのmodule_execメソッドの引数として渡されるので、そのクラスのメソッドを定義していることになります。なのでObjectのメソッド定義になりません。

module RSpec
  module Core
    class ExampleGroup
      def self.define_example_group_method(name, metadata={})
        idempotently_define_singleton_method(name) do |*args, &example_group_block|
# ...
            subclass(self, description, args, registration_collection, &example_group_block)
# ...
        end

        RSpec::Core::DSL.expose_example_group_alias(name)
      end

      def self.subclass(parent, description, args, registration_collection, &example_group_block)
        subclass = Class.new(parent)
        subclass.set_it_up(description, args, registration_collection, &example_group_block)
        subclass.module_exec(&example_group_block) if example_group_block

        MemoizedHelpers.define_helpers_on(subclass)

        subclass
      end
このエントリーをはてなブックマークに追加