Ruby on rails 在Rails中运行rake任务时方法命名空间冲突
使用Rails 2.3.10 如果我的库/任务如下所示Ruby on rails 在Rails中运行rake任务时方法命名空间冲突,ruby-on-rails,rake,Ruby On Rails,Rake,使用Rails 2.3.10 如果我的库/任务如下所示 lib/tasks - a.rake - 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看起来像这样: namesp
lib/tasks
- a.rake
- 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看起来像这样:
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看起来像这样
lib/tasks
- a.rake
- 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
输出是
“从任务B发送的消息:我是任务A”
因此,不会调用a.rake中定义的msg()helper方法。而是调用b.rake中定义的。(更重要的是,如果我有一个c.rake,那么当我运行任务a时,就会调用它的msg helper方法
此方法命名空间是否与已知行为冲突?
我本以为名称空间会阻止这种情况发生
感谢使用如下名称空间:
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文档:
NameSpace类将在NameSpace命令定义的范围内查找任务名称。
您可以创建一个模块以及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
您观察到的是,rake文件命名空间中的方法重新定义了以前定义的同名方法。原因是rake
命名空间
与Ruby命名空间(类或模块)非常不同事实上,它们只充当其中定义的任务名称的名称空间,而不是其他名称空间。因此,如果将任务a
放置在a
名称空间中,但任务之外的其他代码共享同一全局名称空间,则任务a
将成为任务a:a
这一事实,加上Rake在运行给定任务之前加载所有任务的事实,解释了该方法被重新定义的原因
TL;DR:名称冲突的解决方案/提示
您不能期望将两个具有相同名称(或任何其他代码)的方法放在单独的名称空间中,但放在任务之外的方法能够正常工作。不过,以下是一些解决这种情况的提示:
- 将方法放在任务中。如果在
a:a
和b:b
任务中定义了两个msg
方法,则两个rake任务都将正常运行并显示预期的消息
< LI> < P>如果您需要使用RAKE的代码>命名空间< /代码>在多个RAKE任务中,<强>将方法/代码提取到一个真正的Ruby命名空间,如两个模块,和<代码>包含< /代码>需要的任务中的代码。考虑重写样本RAKES:
# 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名称空间
名称不必与Rake文件名相同,因此不能从任务/名称空间名称推断Rake文件名
证明rake的名称空间
s不构成真正的类Ruby名称空间
加载rake文件后,将执行rake DSL语句以及名称空间
方法。该方法获取其中定义的代码块并执行它(使用yield
)在Rake.application
对象的上下文中,该对象是所有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任务本身,情况是不同的。对于每个任务,将创建Rake::task
类(或类似类)的一个独立对象,并在该对象的上下文中运行任务代码。对象的创建在任务管理器中的中完成:
def intern(task_class, task_name)
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
一段来自Rake作者的引文
最后,所有这一切都被一个非常相似和相关的问题所证实,我们可以引用《Rake》的原始作者Jim Weirich的话:
由于名称空间不引入真正的方法作用域,因此作用域的唯一可能是DSL模块
也许有一天,Rake名称空间将成为全类范围的实体,有一个地方可以挂起lazy-let定义,但我们还没有做到这一点
是的,这就是我一直在做的——虽然我的代码片段没有显示出来(我会更新它们)。rake名称空间没有任何效果。