如何使用Ruby元编程来重构这个通用代码?

时间:2010-04-23 05:57:58

标签: ruby metaprogramming rake

我继承了一个项目,其中包含许多写得很糟糕的Rake任务,我需要清理一下。因为Rakefiles是巨大的并且经常容易出现奇怪的无意义依赖,所以我通过将所有内容重构为类来简化和隔离事物。

具体而言,该模式如下:

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/...中的tasks/[namespace].rb中都有相应的类,其类名称类似于XyzAbc

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

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

我仍然希望能够为我定义的任务指定desc - 描述,并且每个任务的描述都不同。当然,我有少量任务完全没有遵循上述模式,所以我将在我的Rakefile中手动指定它们。

我确信这可以通过某种方式进行重构,这样我就不必一遍又一遍地重复同样的习惯用语,但我缺乏经验,看看它是如何完成的。有人可以给我一个帮助吗?

1 个答案:

答案 0 :(得分: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

更新:添加说明。

(请注意,我没有对其进行测试,因此可能存在小错误。)