Using Rails 2.3.10如果我的库/任务看起来像这样
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.耙子看起来像这样
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 时会调用它的 msg 帮助程序方法。
此方法命名空间是否与已知行为冲突?我本以为命名空间会阻止这种情况。
谢谢
您观察到的是,rake 文件命名空间中的方法重新定义了具有相同名称的先前定义的方法。这样做的原因是 Rake namespace
与 Ruby 命名空间(类或模块(有很大不同,实际上它们仅用作其中定义的任务名称的命名空间,而没有其他名称。因此,如果将任务a
放置在 a
命名空间中,则任务将成为任务a:a
,但任务外部的其他代码共享相同的全局命名空间。
这一事实,以及 Rake 在运行给定任务之前加载所有任务的事实,解释了为什么该方法被重新定义。
TL;DR:名称冲突的解决方案/提示
您不能期望放置在单独的namespace
但外部任务中的两个具有相同名称(或任何其他代码(的方法将正常工作。尽管如此,这里有一些提示可以解决这种情况:
-
将方法放在任务中。如果在
a:a
和b: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
由于这两个模块具有不同的名称,并且因为它们在各自的任务中
include
,因此两个 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 的namespace
不构成真正的类似 Ruby 的命名空间
加载 rake 文件后,将执行 Rake DSL 语句以及 namespace
方法。该方法获取其中定义的代码块,并在Rake.application
对象的上下文中执行它(使用yield
(,该对象是所有 rake 任务之间共享的 Rake::Application
类的单例对象。没有为命名空间创建动态模块/类,它只是在主对象上下文中执行。
# 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 任务确实构成了 ruby 命名空间
但是,对于耙任务本身,情况就不同了。对于每个任务,将创建Rake::Task
类(或类似类(的单独对象,并且任务代码在此对象的上下文中运行。对象的创建是在任务管理器的intern
方法中完成的:
def intern(task_class, task_name)
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
耙子作者的引述
最后,所有这一切都在 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