Rails 7.1 db:prepare to load schema if the database already exists but is empty

What is rails db:prepare?

In Rails 6.1, the rails db:prepare command was introduced to prepare the database for use. This command idempotently sets up a database (creating the database, loading schema, and/or migrations). Presently, if the database exists and has tables that are not populated, db:prepare will run all the migrations to bring the database up to the state. This may not be ideal in situations where we might have pruned old migration files, or if the database is provisioned by a Platform as a Service (PaaS) provider. During initial setup, PaaS providers provision an empty database. Running db:prepare will run all the migrations which may not be as efficient as loading the schema.

Before

Let’s assume in our Rails application we have two migrations, one to create model Product and another to create model User.

# Generate the model for Product
rails g model Product title:string

# Prepare the database
rails db:prepare
== 20230201061344 CreateProducts: migrating ======================================
-- create_table(:products)
   -> 0.0027s
== 20230201061344 CreateProducts: migrated (0.0028s) =============================

# Drop the database
rails db:drop
Dropped database 'rails-7-demo_development'

# Generate the model for User
rails g model User name:string

# Prepare the database
rails db:prepare

== 20230201061344 CreateProducts: migrating ======================================
-- create_table(:products)
   -> 0.0019s
== 20230201061344 CreateProducts: migrated (0.0021s) =============================

== 20230201061529 CreateUsers: migrating ====================================
-- create_table(:users)
   -> 0.0015s
== 20230201061529 CreateUsers: migrated (0.0016s) ===========================

In the above example, after dropping the database, we generated a new model User, and ran the rails db:prepare command. This command ran both migrations, even though the database was empty.

After

However in the upcoming Rails 7.1, if we were to run the same commands, the rails db:prepare command will load the schema and run the remaining migrations.

# Generate the model for Product
rails g model Product title:string

# Prepare the database
rails db:prepare
== 20230201061344 CreateProducts: migrating ======================================
-- create_table(:products)
   -> 0.0027s
== 20230201061344 CreateProducts: migrated (0.0028s) =============================

# Drop the database
rails db:drop
Dropped database 'rails-7-demo_development'

# Generate the model for User
rails g model User name:string

# Prepare the database
rails db:prepare

== 20230201061529 CreateUsers: migrating ====================================
-- create_table(:users)
   -> 0.0015s
== 20230201061529 CreateUsers: migrated (0.0016s) ===========================

In the above example, after dropping the database, when rails db:prepare is run, it only runs the migration to create the User model.

However, if we were to look at the schema, we would see that both tables are present.

ActiveRecord::Schema.define(version: 2021_02_03_061529) do

  create_table "products", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

end

This is because the rails db:prepare command will now load the schema and then run the remaining migrations.

If the database exists but is empty, the rails db:prepare command will simply load the schema without running any migrations

# Generate the model for Product
rails g model Product title:string

# Prepare the database
rails db:prepare

# Drop the database
rails db:drop

# Create the database
rails db:create

# Prepare the database
rails db:prepare

# There will be no output since no migrations will be run
# Schema.rb
ActiveRecord::Schema.define(version: 2021_02_03_061529) do
  create_table "products", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
end

This change is achieved by using the value of ActiveRecord::SchemaMigration.table_exists? to determine if the database has been populated with tables. If the value is false, the db:prepare task will load the schema, and then run any remaining migrations. Else, db:prepare will continue to work as it did.

Check out the PR for more details.

Need help on your Ruby on Rails or React project?

Join Our Newsletter