Rake::Task .enhance() Method Explained
Tags: ruby, rails, rake
Published: 2020-08-05 18:33:09

I recently fixed a bug in our [Forem software](https://github.com/forem/forem) that led to some serious Ruby learnings that I have to share.

# The Bug

The problem we were having was that a Postgres view we use to expose data to our team, [called `hypershield`](https://github.com/ankane/hypershield), was not getting refreshed correctly. Ideally, we want this view refreshed AFTER we run migrations so that it is up to date with the database. However, it came to my attention when looking at our logs that the `hypershield` view was being refreshed BEFORE we ran migrations. 
```
[hypershield] Refreshing schemas
[hypershield] Success!
== 20200726215928 ChangeTagIdsToBigints: migrating ============================
== 20200726215928 ChangeTagIdsToBigints: migrated (4.9509s) ===================
```

This led to our `hypershield` views being out of date with our actual database. 

# .enhance()

To ensure that the `hypershield` view is refreshed when we migrate our database we use the [Rake::Task method `.enhance()`](https://www.rubydoc.info/gems/rake/Rake/Task#enhance-instance_method). 
```ruby
Rake::Task["db:prepare"].enhance(["hypershield:refresh"])
```
In the past when I used the method `enhance` it always ran the additional rake task AFTER the task I was "enhancing". This got me really confused as to why the behavior was suddenly different, so I went digging. 

During my digging, I came across the Rake::Task docs. Here, I opened up the source code for the method.
```ruby
def enhance(deps=nil, &block)
  @prerequisites |= deps if deps
  @actions << block if block_given?
  self
end
```  
The first thing that struck me was that the behavior was different if you passed in an argument versus a block. 

When passed an argument, that argument became one of the `@prerequisites` for the task, meaning it was run BEFORE. When passed a block, the block was added to a list of `@actions`. According to the docs, `@actions` are "attached to a task", meaning they run AFTER the task. 

# The Fix

If I want to refresh our `hypershield` view after we run migrations I need to pass that refresh task to `enhance` in a block and not as an argument. The [final fix](https://github.com/forem/forem/pull/9546) looks like this:

```ruby
Rake::Task["db:prepare"].enhance do
  Rake::Task["hypershield:refresh"].execute
end
```

Now, the `hypershield` view is updated AFTER migrations are run. This ensures the view is always up to date with our database. **NOTE** the `.execute` that we have to use to invoke our task. This is not needed when you pass the task as an argument, but it is needed when you are using a block. 

# TL;DR

Passing a task as an argument to `enhance` causes it to run BEFORE the task you are "enhancing".
```ruby
Rake::Task["task_A"].enhance(["task_B"])
# Runs task_B
# Runs task_A
```
Passing a task to `enhance` in a block causes it to run AFTER the task you are "enhancing".
````ruby
Rake::Task["task_A"].enhance do
  Rake::Task["task_B"].execute
end
# Runs task_A
# Runs task_B
```

Now go and enhance away!!!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6tuyrapc49w9prajk3va.gif)