使用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辅助方法在我运行任务时被调用。
此方法命名空间是否与已知行为发生冲突? 我原本以为命名空间会阻止这个。
由于
答案 0 :(得分:14)
您观察到rake文件名称空间中的方法重新定义以前定义的具有相同名称的方法。这样做的原因是Rake namespace
与Ruby命名空间(类或模块)非常不同,实际上它们仅作为命名空间用于其中定义的任务的名称, 但没有别的。因此,如果a
置于a:a
命名空间中,则任务a
成为任务namespace
,但任务之外的其他代码共享相同的全局命名空间。
这一事实,以及 Rake在运行给定任务之前加载所有任务的事实,解释了为什么重新定义该方法的原因。
您不能指望将两个具有相同名称(或任何其他代码)的方法放在单独的msg
内但外部任务中才能正常工作。不过,这里有一些解决这种情况的提示:
将方法放在任务中。如果在a:a
和b: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任务将再次按预期运行。
现在,让我们在源代码的帮助下证明上述声明......
这个很容易。在主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文件名。
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
尽管有自己的Rake任务,情况却有所不同。对于每个任务,创建{strong> Rake::Task
类(或类似类)的单独的对象,并在该对象的上下文中运行任务代码。对象的创建在任务管理器的intern
method中完成:
def intern(task_class, task_name)
@tasks[task_name.to_s] ||= task_class.new(task_name, self)
end
最后,所有这些都得到了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