使用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
输出是
“MSG从任务B出发:我是任务”
因此,a.rake中定义的msg()辅助方法不会被调用。而是在b.rake中定义的那个被调用。 (更重要的是,如果我有一个c.rake - 那么 它的 当我运行任务a时调用msg帮助器方法。
这个方法命名空间是否会破坏已知行为?
我原本以为命名空间会阻止这个。
谢谢
你观察到的是 rake文件命名空间中的方法重新定义 以前定义的具有相同名称的方法。原因是Rake namespace
s与Ruby名称空间(类或模块)非常不同,实际上它们只是作为一个 命名空间,用于在其中定义的任务的名称, 但没有别的。因此,任务 a
成为任务 a:a
如果放在 a
命名空间,但任务外的其他代码共享 相同 全局命名空间
这个事实,加上事实 Rake加载所有任务 在运行给定任务之前,解释为什么重新定义该方法。
TL; DR:名称冲突的解决方案/提示
您不能指望将两个具有相同名称(或任何其他代码)的方法放在一起分开 namespace
s但外部任务将正常工作。不过,这里有几个提示可以解决这种情况:
放置方法 在任务内部。如果两者 msg
方法是在里面定义的 a:a
和 b:b
任务,然后两个rake任务都将正常运行并显示预期的消息。
如果你需要使用rake的代码 namespace
在多个rake任务中, 将方法/代码提取到真正的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
因为这两个模块有不同的名称,因为它们是 include
d在相应的任务中,两个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文件名。
耙的证明 namespace
s不构成真正的类Ruby命名空间
加载rake文件后,执行Rake DSL语句,并执行 namespace
方法。该方法获取其中定义的代码块并执行它(使用 yield
) 在。的背景下 Rake.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
查看相关来源 这里 和 这里。
Rake任务DO构成ruby命名空间
有了Rake任务本身,情况就不同了。对于每项任务, 一个单独的对象 的 Rake::Task
创建类(或类似的类),并在该对象的上下文中运行任务代码。对象的创建是在 intern
方法 在任务管理器中:
def intern(task_class, task_name)
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
引用Rake的作者
最后,所有这些都得到了证实 关于github的有趣讨论 这涉及一个非常相似和相关的问题,我们可以从中引用Rake的原作者Jim Weirich:
由于名称空间不引入实际方法范围,因此范围的唯一真正可能性是DSL模块。
...
也许,有一天,Rake命名空间将成为完整的类范围实体,可以挂起懒惰的定义,但我们还没有。
你观察到的是 rake文件命名空间中的方法重新定义 以前定义的具有相同名称的方法。原因是Rake namespace
s与Ruby名称空间(类或模块)非常不同,实际上它们只是作为一个 命名空间,用于在其中定义的任务的名称, 但没有别的。因此,任务 a
成为任务 a:a
如果放在 a
命名空间,但任务外的其他代码共享 相同 全局命名空间
这个事实,加上事实 Rake加载所有任务 在运行给定任务之前,解释为什么重新定义该方法。
TL; DR:名称冲突的解决方案/提示
您不能指望将两个具有相同名称(或任何其他代码)的方法放在一起分开 namespace
s但外部任务将正常工作。不过,这里有几个提示可以解决这种情况:
放置方法 在任务内部。如果两者 msg
方法是在里面定义的 a:a
和 b:b
任务,然后两个rake任务都将正常运行并显示预期的消息。
如果你需要使用rake的代码 namespace
在多个rake任务中, 将方法/代码提取到真正的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
因为这两个模块有不同的名称,因为它们是 include
d在相应的任务中,两个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文件名。
耙的证明 namespace
s不构成真正的类Ruby命名空间
加载rake文件后,执行Rake DSL语句,并执行 namespace
方法。该方法获取其中定义的代码块并执行它(使用 yield
) 在。的背景下 Rake.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
查看相关来源 这里 和 这里。
Rake任务DO构成ruby命名空间
有了Rake任务本身,情况就不同了。对于每项任务, 一个单独的对象 的 Rake::Task
创建类(或类似的类),并在该对象的上下文中运行任务代码。对象的创建是在 intern
方法 在任务管理器中:
def intern(task_class, task_name)
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
引用Rake的作者
最后,所有这些都得到了证实 关于github的有趣讨论 这涉及一个非常相似和相关的问题,我们可以从中引用Rake的原作者Jim Weirich:
由于名称空间不引入实际方法范围,因此范围的唯一真正可能性是DSL模块。
...
也许,有一天,Rake命名空间将成为完整的类范围实体,可以挂起懒惰的定义,但我们还没有。
使用如下命名空间:
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
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