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

发布于 2024-08-29 14:04:56 字数 1534 浏览 2 评论 0原文

我继承了一个项目,其中有很多写得很糟糕的 Rake 任务,我需要对其进行一些清理。由于 Rakefile 非常庞大,而且常常容易出现奇怪的、无意义的依赖关系,因此我通过将所有内容重构为类来简化和隔离一些事情。

具体来说,该模式如下:

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] 的 tasks/... 中都有一个对应的类.rb,类名类似于 XyzAbc

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

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

我仍然希望能够为我定义的任务指定一个 desc 描述,并且每个任务的描述都不同。当然,我有一小部分任务根本不遵循上述模式,因此我将在 Rakefile 中手动指定这些任务。

我确信这可以以某种方式重构,这样我就不必一遍又一遍地重复相同的习惯用法,但我缺乏经验来了解如何做到这一点。有人可以帮我吗?

I inherited a project with a lot of badly-written Rake tasks that I need to clean up a bit. Because the Rakefiles are enormous and often prone to bizarre nonsensical dependencies, I'm simplifying and isolating things a bit by refactoring everything to classes.

Specifically, that pattern is the following:

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.

In tasks/foobar.rb, I have something that looks like this:

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

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

For me, this is great, because it allows me to separate the task dependencies from each other and to move them to another location entirely, and I've been able to drastically simplify things and isolate the dependencies. The Rakefile doesn't hit a require until you actually try to run a task. Previously this was causing serious issues because you couldn't even list the tasks without it blowing up.

My problem is that I'm repeating this idiom very frequently. Notice the following patterns:

  • For every namespace :xyz_abc, there is a corresponding class in tasks/... in the file tasks/[namespace].rb, with a class name that looks like XyzAbc.

  • For every task in a particular namespace, there is an identically named method in the associated namespace class. For example, if namespace :foo_bar has a task :apples, you would expect to see def apples() ... inside the FooBar class, which itself is in tasks/foo_bar.rb.

  • Every task :t defines a "meta-task" _t (that is, the task name prefixed with an underscore) which is used to do the actual work.

I still want to be able to specify a desc-description for the tasks I define, and that will be different for each task. And, of course, I have a small number of tasks that don't follow the above pattern at all, so I'll be specifying those manually in my Rakefile.

I'm sure that this can be refactored in some way so that I don't have to keep repeating the same idiom over and over, but I lack the experience to see how it could be done. Can someone give me an assist?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

著墨染雨君画夕 2024-09-05 14:04:56

像这样的东西应该对你有用。

# 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

更新:添加了描述。

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

Something like this should work for you.

# 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

Update: added descriptions.

(Note that I haven't tested it, so there might be small errors in there.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文