2010-04-23 48 views
3

我继承了一个项目,其中包含许多写得很糟糕的Rake任务,我需要清理一下。因为Rakefiles是巨大的,并且往往容易出现奇怪的无意义的依赖关系,所以我通过将所有东西重构为类来简化和隔离事情。如何使用Ruby元编程来重构此通用代码?

具体而言,这种模式是这样的:

namespace :foobar do 
    desc "Frozz the foobar." 
    task :frozzify do 
    unless Rake.application.lookup('_frozzify') 
     require 'tasks/foobar' 
     Foobar.new.frozzify 
    end 
    Rake.application['_frozzify'].invoke 
    end 

    # Above pattern repeats many times. 
end 

# Several namespaces, each with tasks that follow this pattern. 

tasks/foobar.rb,我有一些看起来像这样:

class Foobar 
    def frozzify() 
    # The real work happens here. 
    end 

    # ... Other tasks also in the :foobar namespace. 
end 

对于我来说,这是伟大的,因为它可以让我分开任务依赖关系以及将它们完全移动到另一个位置,并且我已经能够彻底地简化事物并隔离依赖关系。在您真正尝试运行任务之前,Rakefile未达到require。以前这是造成严重问题的原因,因为如果没有炸毁任务,你甚至无法列出任务。

我的问题是我非常频繁地重复这个习语。请注意以下模式:

  • 对于每一个命名空间:xyz_abc,有文件tasks/[namespace].rbtasks/...相应的类,它有一个类名,看起来像XyzAbc

  • 对于特定名称空间中的每个任务,在关联的名称空间类中都有一个名称相同的方法。例如,如果名称空间:foo_bar有一个任务:apples,则预计在FooBar类中看到def apples() ...,该类本身在tasks/foo_bar.rb中。

  • 每个任务:t定义了一个用于完成实际工作的“元任务”_t(即任务名称以下划线为前缀)。

我还是希望能够指定一个desc -description为我定义的任务,这将是为每个任务不同。而且,当然,我有一小部分任务完全不遵循上述模式,所以我将在我的Rakefile中手动指定这些任务。

我相信这可以通过某种方式进行重构,所以我不必一直重复相同的习语,但是我缺乏经验来看它如何完成。有人可以给我一个协助吗?

回答

4

这样的事情应该适合你。

# Declaration of all namespaces with associated tasks. 
task_lists = { 
    :foobar => { 
    :task_one => "Description one", 
    :task_two => "Description two", 
    :frozzify => "Frozz the foobar", 
    :task_three => "Description three" }, 
    :namespace_two => { 
    :task_four => "Description four", 
    :etc => "..."} } 

# For every namespace with list of tasks... 
task_lists.each do |ns, tasks| 
    # In the given the namespace... 
    namespace ns do 
    # For every task in the task list... 
    tasks.each do |task_name, description| 
     # Set the task description. 
     desc description 
     # Define the task. 
     task task_name do 
     unless Rake.application.lookup("_#{task_name}") 
      # Require the external file identified by the namespace. 
      require "tasks/#{ns}" 
      # Convert the namespace to a class name and retrieve its associated 
      # constant (:foo_bar will be converted to FooBar). 
      klass = Object.const_get(ns.to_s.gsub(/(^|_)(.)/) { $2.upcase }) 
      # Create a new instance of the class and invoke the method 
      # identified by the current task. 
      klass.new.send(task_name) 
     end 
     Rake.application["_#{task_name}"].invoke 
     end 
    end 
    end 
end 

更新:补充说明。

(请注意,我没有测试过,所以有可能在那里小错误。)

+0

这肯定看起来像它的工作,但他会如何,如果他去这条路线指定任务描述? – 2010-04-23 11:52:28

+0

@John,好点。我用散列替换了任务数组。这样也有一些方法来设置描述。然而,这可能会变得越来越模糊,所以在某种程度上可能会创建某种API。 – molf 2010-04-23 14:33:27