In this post, we will see an approach how to safely drop a table from a production application without running into weird issues.
As an example, we will use a simple blog that allows adding comments to each post and also has a simple stats table where it tracks likes, views, and shares of posts.
The model diagram for this example is the following:
From the application, we are interested in the schema file and the Stat class:
# db/schema.rb ActiveRecord::Schema.define(version: 2018_08_08_153342) do create_table "comments", force: :cascade do |t| t.string "username" t.string "content" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "post_id" t.index ["post_id"], name: "index_comments_on_post_id" end create_table "posts", force: :cascade do |t| t.string "title" t.string "content" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "stats", force: :cascade do |t| t.integer "likes" t.integer "views" t.integer "shares" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "post_id" t.index ["post_id"], name: "index_stats_on_post_id" end end
The stat class:
# app/models/stat.rb class Stat < ApplicationRecord belongs_to :post class << self def on_like(post_id) stat = Stat.find_by(post_id: post_id) stat.increment!(:likes) end def on_view(post_id) stat = Stat.find_by(post_id: post_id) stat.increment!(:views) end def on_share(post_id) stat = Stat.find_by(post_id: post_id) stat.increment!(:shares) end end end
Our task is to remove the stats table because we will be using a third party provider for tracking. So let’s get started:
1. Search and Update
This is the most crucial step, it depends on the project size and it requires patience and precision.
Generally, we want to perform the search using this order:
- The class name to delete
- Possible association of the model
- Method names of the model
At this point, we will have some failing tests, so we need to update those tests to make the suite green again!.
We should keep in mind that we should also do some manual testing too. It’s not just clicking through the application but we need to keep an eye on our logs to check for warnings.
3. Check background jobs
This step depends on the project, in our example let’s pretend that we have a background job that processes our blog stats weekly to generate a report.
# app/jobs/generate_stats_report_worker.rb class GenerateStatsReportWorker include Sidekiq::Worker def perform(stat_ids) stats = Stat.where(id: stat_ids) reports = generate_report(stats) send_reports end end
This worker will be scheduled to execute with an array of Stat ids as parameters. It’s clear that we will have a lot of errors if we drop the Stat model while this job is still in the scheduled queue. So what to do in this case?
There is no concrete answer to this question, it depends on the importance of the background job, the best tip for this case is to check with your project manager or your senior colleagues. But the main two options are:
- Delete the enqueued jobs
- Run the enqueued jobs before deploying the changes.
Finally, we reached the last step in the process, we are ready now to deploy our changes.
So it’s time to add the migration:
class DropStatsTable < ActiveRecord::Migration[5.2] def change drop_table :stats end end
There are different recipes in deployment, so to be sure that we don’t end up in a state where our code in production relies on the existence of Stats table while its dropped, we need to submit two pull requests.
- The first pull request will include only code changes without dropping the table
- The second pull request will include the drop migration
That’s it, we saw together a four steps process to delete a table from production with confidence! So have fun and keep coding 🙂