Как я могу использовать метапрограммирование Ruby для рефакторинга этого общего кода? - PullRequest
3 голосов
/ 23 апреля 2010

Я унаследовал проект с множеством плохо написанных Rake-задач, которые мне нужно немного очистить. Поскольку Rake-файлы огромны и часто склонны к причудливым бессмысленным зависимостям, я упрощаю и изолирую вещи, переводя все в классы.

В частности, этот шаблон выглядит следующим образом:

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].rb имеется соответствующий класс *1019* с именем класса, которое выглядит как XyzAbc.

  • Для каждой задачи в определенном пространстве имен в связанном классе пространства имен есть метод с одинаковым именем. Например, если у пространства имен :foo_bar есть задача :apples, вы ожидаете увидеть def apples() ... внутри класса FooBar, который сам по себе находится в tasks/foo_bar.rb.

  • Каждая задача :t определяет «мета-задачу» _t (то есть имя задачи с префиксом подчеркивания), которая используется для выполнения фактической работы.

Я все еще хочу иметь возможность указать desc -описание для задач, которые я определяю, и это будет отличаться для каждой задачи. И, конечно, у меня есть небольшое количество задач, которые вообще не следуют вышеуказанному шаблону, поэтому я буду указывать их вручную в моем Rakefile.

Я уверен, что это можно каким-то образом реорганизовать, чтобы мне не приходилось повторять одну и ту же идиому снова и снова, но мне не хватает опыта, чтобы понять, как это можно сделать. Может ли кто-нибудь оказать мне помощь?

1 Ответ

4 голосов
/ 23 апреля 2010

Что-то вроде этого должно работать для вас.

# 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

Обновление: добавлены описания.

(Обратите внимание, что я не проверял это, поэтому там могут быть небольшие ошибки.)

...