# Using metaprogramming to refactor many similar Rake tasks?

**URL:** <https://www.ruby-forum.com/t/using-metaprogramming-to-refactor-many-similar-rake-tasks/187441>\
**Category:** Ruby\
**Created:** [April 23, 2010, 8:15am UTC](https://www.ruby-forum.com/t/using-metaprogramming-to-refactor-many-similar-rake-tasks/187441 "2010-04-23T08:15:45Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![aftershock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/8e7dd6/32.png) [@aftershock](https://www.ruby-forum.com/u/aftershock)\
**Post date:** [April 23, 2010, 8:15am UTC](https://www.ruby-forum.com/t/using-metaprogramming-to-refactor-many-similar-rake-tasks/187441/1 "2010-04-23T08:15:45Z")

</div>

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 relatively sure that this can be refactored with metaprogramming  
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, or  
whether I’m even on the right track at all. Any thoughts or advice to  
point me in the right direction?

Thanks!

– James

---

<div class="post-metadata">

**Author:** ![aftershock](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/8e7dd6/32.png) [@aftershock](https://www.ruby-forum.com/u/aftershock)\
**Post date:** [April 23, 2010, 5:51pm UTC](https://www.ruby-forum.com/t/using-metaprogramming-to-refactor-many-similar-rake-tasks/187441/2 "2010-04-23T17:51:13Z")

</div>

On 4/22/10, James W. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> ```
> end
> 
> # Above pattern repeats many times.
> end
> 
> # Several namespaces, each with tasks that follow this pattern.
> 
> ```

There may be some feature of rake that helps dry up code like this…  
I wouldn’t know. But if not, maybe something like this would help:  
(untested, you need to customize it anyway)

def declare\_tasks namespace, \*tasks  
eval %{  
namespace :#{namespace.downcase} do  
#{tasks.map{|task|  
\<\<-END  
desc(“#{task.capitalize} the #{namespace}”)  
task :#{task}ify do  
unless Rake.application.lookup(‘_#{task}ify’)  
require ‘tasks/#{namespace.downcase}’  
#{namespace}.new.#{task}ify  
end  
Rake.application['_#{task}ify’].invoke  
end  
END  
}.join  
}  
end  
}  
end

declare\_task “Foobar”, “frozz”, “frotz”, “xyzzy”

Eval is the heavy guns of meta-programming. Instead of downcase, you  
need to use the rails camel-case-to-snake-case conversion method. I  
forget the name. Also, you could dry it up more by scanning the tasks/  
directory for namespaces and tasks to define… but that might be  
kind of hard.
