在Rails中运行rake任务时方法命名空间冲突

时间:2011-08-24 18:53:18

标签: ruby-on-rails rake

使用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

然后当我运行任务时

rake a RAILS_ENV=sandbox

输出是 “MSG从任务B发出:我是任务”

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

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

由于

3 个答案:

答案 0 :(得分:14)

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

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

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

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

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

  • 如果您需要在多个rake任务中使用rake的include中的代码,将方法/代码提取到真正的Ruby命名空间,例如两个模块,并且# 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 需要它的任务中的代码。考虑一下样本耙的重写:

    include

    由于这两个模块具有不同的名称,并且因为它们在各自的任务中都是Rakefile d,因此两个rake任务将再次按预期运行。

现在,让我们在源代码的帮助下证明上述声明......

证明Rake首先加载所有任务以及为什么这样做

这个很容易。在主Rails.application.load_tasks 中,您总能找到以下行:

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

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

lib/tasks

因此,它在b.rake目录中搜索所有rake文件,并按排序顺序逐个加载它们。这就是a.rake文件将在a.rake之后加载的原因,并且它内部的任何内容都可能会重新定义namespace和之前加载的所有rake文件中的代码。

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

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

加载rake文件后,执行Rake DSL语句,以及yield方法。该方法获取其中定义的代码块并在Rake.application对象的上下文中执行它(使用Rake::Application,这是{{1}的单个对象在所有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任务DO​​构成ruby名称空间

尽管有自己的Rake任务,情况却有所不同。对于每个任务,创建{strong> 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的确认,这个问题处理了一个非常相似和相关的问题,我们可以引用它来引用Rake的原作者Jim Weirich:

  

由于名称空间不引入实际方法范围,因此范围的唯一真正可能性是DSL模块。

     

...

     

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

答案 1 :(得分:0)

使用如下命名空间:

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

答案 2 :(得分:0)

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