I recently fixed a bug in our Forem software 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
, 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()
.
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.
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 looks like this:
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".
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".
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)
Top comments (4)
TIL,
enhance
existed. πππ½Meow that's a fitting GIF right there.
Thank you Molly! Super useful - sneaky differences in behavior! π
Great post