I recently fixed a bug in our Forem software that led to some serious Ruby learnings that I have to share.
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.
To ensure that the
hypershield view is refreshed when we migrate our database we use the Rake::Task method
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.
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
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.
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!!!