【问题标题】:Method namespace clashing when running rake tasks in Rails在 Rails 中运行 rake 任务时方法命名空间冲突
【发布时间】:2011-08-24 18:53:17
【问题描述】:

使用 Rails 2.3.10 如果我的 lib/tasks 看起来像这样

lib/tasks
- a.rake
- b.rake

a.rake 看起来像这样:

namespace :a do
    desc "Task A"
    task(:a=>:environment)do
      msg('I AM TASK A')
    end

    def msg(msg)
      puts "MSG SENT FROM Task A: #{msg}"
    end
end

b.rake 看起来像这样

namespace :b do
    desc "Task B"
    task(:b=>:environment)do
      msg('I AM TASK B')
    end

    def msg(msg)
      puts "MSG SENT FROM Task B: #{msg}"
    end
end

然后当我运行任务a时

rake a RAILS_ENV=sandbox

输出是 “任务 B 发送的消息:我是任务 A”

因此,a.rake 中定义的 msg() 辅助方法不会被调用。而是调用 b.rake 中定义的内容。 (更重要的是,如果我有一个 c.rake - 那么当我运行任务 a 时 it's msg helper 方法会被调用。

此方法命名空间是否与已知行为发生冲突? 我原以为命名空间会阻止这种情况发生。

谢谢

【问题讨论】:

    标签: ruby-on-rails rake


    【解决方案1】:

    您观察到的是 rake 文件命名空间中的方法重新定义了以前定义的具有相同名称的方法。这样做的原因是 Rake namespaces 与 Ruby 命名空间(类或模块)非常不同,实际上它们仅作为 其中定义的任务名称的命名空间但没有别的。因此,如果将任务 a 放置在 a 命名空间中,则任务 a:a 将变为任务 a:a,但任务之外的其他代码共享 same 全局命名空间。

    这一事实,连同 Rake 在运行给定任务之前加载所有任务 的事实,解释了为什么要重新定义该方法。

    TL;DR:名称冲突的解决方案/提示

    您不能期望将两个具有相同名称(或任何其他代码)的方法放在单独的 namespaces 中,但外部任务会正常工作。不过,这里有一些提示可以解决这种情况:

    • 将方法放在任务中。如果在a:ab:b 任务中定义了两个msg 方法,那么这两个rake 任务都将正常运行并显示预期的消息。

    • 如果您需要在多个 rake 任务中使用来自 rake 的 namespace 的代码,将方法/代码提取到真正的 Ruby 命名空间,例如两个模块,include 的代码在需要它的任务中。考虑对样本耙的这种重写:

      # lib/tasks/a.rake:
      module HelperMethodsA
        def msg(msg)
          puts "MSG SENT FROM Task A: #{msg}"
        end
      end
      
      namespace :a do
        desc "Task A"
        task(:a => :environment) do
          include HelperMethodsA
          msg('I AM TASK A')
        end
      end
      
      # lib/tasks/b.rake:
      module HelperMethodsB
        def msg(msg)
          puts "MSG SENT FROM Task B: #{msg}"
        end
      end
      
      namespace :b do
        desc "Task B"
        task(:b => :environment) do
          include HelperMethodsB
          msg('I AM TASK B')
        end
      end
      

      因为这两个模块有不同的名称,并且因为它们在各自的任务中是included,所以两个 rake 任务将再次按预期运行。

    现在,让我们借助源代码来证明上述说法...

    证明 Rake 首先加载所有任务以及这样做的原因

    这个很简单。在主要的Rakefile 中,您总能找到以下行:

    Rails.application.load_tasks
    

    该方法最终从 Rails 引擎调用以下代码:

    def run_tasks_blocks(*) #:nodoc:
      super
      paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
    end
    

    因此,它会在 lib/tasks 目录中搜索所有 rake 文件,并按排序顺序依次加载它们。这就是为什么 b.rake 文件将在 a.rake 之后加载,并且其中的任何内容都可能重新定义 a.rake 和所有以前加载的 rake 文件的代码。

    Rake 必须加载所有 rake 文件,因为 rake namespace 名称不必与 rake 文件名相同,因此无法从任务/命名空间名称推断 rake 文件名。

    证明 rake 的 namespaces 不构成真正的类 Ruby 命名空间

    加载 rake 文件后,将执行 Rake DSL 语句以及 namespace 方法。该方法获取其中定义的代码块并执行它(使用yieldRake.application 对象的上下文中,这是Rake::Application 类的一个单例对象,在它们之间共享所有 rake 任务。没有为命名空间创建动态模块/类,它只是在主对象上下文中执行。

    # this reuses the shared Rake.application object and executes the namespace code inside it
    def namespace(name=nil, &block)
      # ...
      Rake.application.in_namespace(name, &block)
    end
    
    # the namespace block is yielded in the context of Rake.application shared object
    def in_namespace(name)
      # ...
      @scope = Scope.new(name, @scope)
      ns = NameSpace.new(self, @scope)
      yield(ns)
      # ...
    end
    

    查看相关来源herehere

    Rake 任务确实构成了 ruby​​ 命名空间

    不过,对于 Rake 任务本身,情况有所不同。对于每个任务,将创建Rake::Task 类(或类似类)的一个单独的对象,并且任务代码在该对象的上下文中运行。对象的创建在任务管理器中的intern method中完成:

    def intern(task_class, task_name)
      @tasks[task_name.to_s] ||= task_class.new(task_name, self)
    end
    

    Rake 的作者的一句话

    最后,这一切都得到了interesting discussion on github 的证实,该interesting discussion on github 处理了一个非常相似和相关的问题,我们可以引用 Rake 的原作者 Jim Weirich 的话:

    由于命名空间不引入真正的方法作用域,作用域唯一真正的可能性是 DSL 模块。

    ...

    也许,有一天,Rake 命名空间将成为完整的类范围实体,并有一个地方可以挂起惰性 let 定义,但我们还没有。

    【讨论】:

      【解决方案2】:

      使用如下命名空间:

      namespace :rake_a do
        desc "Task A"
        task(:a=>:environment)do
          msg('I AM TASK A')
        end
      
        def msg(msg)
          puts "MSG SENT FROM Task A: #{msg}"
        end
      end
      

      【讨论】:

      • 是的,这就是我一直在做的事情——尽管我的代码 sn-ps 没有显示(我会更新它们)。 rake 命名空间无效。
      【解决方案3】:

      rake 命名空间仅用于 rake 任务。 请参阅 Rake 文档: The NameSpace class will lookup task names in the the scope defined by a namespace command.

      您可以创建一个模块以及您的 rake 命名空间来解决这个问题:

      module A do
      
          module_functions
      
          def msg(msg)
            puts "MSG SENT FROM Task A: #{msg}"
          end
      end
      
      namespace :a do
          desc "Task A"
          task(:a=>:environment)do
             A.msg('I AM TASK A')
          end
      end
      

      【讨论】:

        猜你喜欢
        • 2016-10-02
        • 1970-01-01
        • 1970-01-01
        • 2010-12-07
        • 1970-01-01
        • 1970-01-01
        • 2010-09-20
        • 2012-09-10
        相关资源
        最近更新 更多