Drop a table in production

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:

model diagram Class diagram

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"

  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

  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"


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)

    def on_view(post_id)
      stat = Stat.find_by(post_id: post_id)

    def on_share(post_id)
      stat = Stat.find_by(post_id: post_id)


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

2. Testing

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)


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.

4. Deployment

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

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.

  1. The first pull request will include only code changes without dropping the table
  2. 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 🙂