Modifying Existing Migrations in Ruby

Periodically, you will make mistakes when writing migrations. If you’ve already run a migration, you can’t just edit the migration and run it again: Rails will assume it has already run a migration and will do nothing when you run rails db:migrate. You must rollback the migration (using bin/rails db:rollback, for example), edit the migration, and then run rails db:migrate to run the corrected version.

In general, editing existing migrations is not a good idea. You will create extra work for yourself and your colleagues, and cause a lot of headaches if the existing version of the migration has already been run in production. Instead, you should write a new migration that implements the required changes. Editing a newly generated migration that hasn’t been committed to version control yet (or at least not past your production machine) is relatively harmless.

The revert method can be very useful when writing a new migration to revert a previous migration in whole or in part (see Reverting to previous migrations).

(Schema Dumping and You) Export Schema

What are schema files for?

Migrations, as powerful as they are, are not an authoritative source for your database schema. This role goes to either the db/schema.rb file or the SQL file that Active Record generates when it examines the database. They are not designed for editing, they just reflect the current state of the database.

It is not necessary (which can lead to an error) to deploy a new instance of the application by applying the entire migration history. It is much easier and faster to load a description of the current schema into the database.

For example, how a test database is created: the current working database is unloaded (either to db/schema.rb or db/structure.sql) and then loaded into the test database.

Schema files are also useful if you want to take a quick look at what attributes an Active Record object has. This information is not contained in the model code and is often spread across multiple migrations, but brought together in a schema file. There is an annotate_models gem that automatically adds and updates comments at the beginning of each of the models that make up the schema if you want this functionality.

Types of schema uploads

There are two ways to download a schema. They are set in config/environment.rb in the config.active_record.schema_format property, which can be either :sql or :ruby.

If :ruby is selected, then the schema is stored in db/schema.rb. Looking at this file, you can see that it is very similar to one big migration:

ActiveRecord::Schema.define(version: 20080906171750) do

  create_table “authors”, force: true do |t|

    t.string “name”

    t.datetime “created_at”

    t.datetime “updated_at”

  end

  create_table “products”, force: true do |t|

    t.string “name”

    t.text “description”

    t.datetime “created_at”

    t.datetime “updated_at”

    t.string “part_number”

  end

end

In many cases this is sufficient. This file is created with a database check and describes its structure using create_table , add_index and so on. Since it is independent of the database type, it can be loaded into any database supported by Active Record. This is very useful if you are distributing an application that can run on different databases.

NOTE: db/schema.rb cannot describe database-specific elements such as triggers, sequences, stored procedures, CHECK constraints, and so on. Note that while you can execute arbitrary SQL statements in migrations, these statements cannot be played back by the schema unloader. If you use these features, you need to set the schema format to :sql.

Instead of using the Active Records schema unloader, the database structure will be dumped using the database specific tool (using the rails db:structure:dump task) in db/structure.sql. For example, for PostgreSQL, the pg_dump utility is used. For MySQL and MariaDB, this file will contain the result of SHOW CREATE TABLE for different tables.

Loading such schemas is simply running the SQL statements they contain. By definition, an exact copy of the database structure will be created. Using the schema :sql format, however, prevents the schema from being loaded into the DBMS other than the one used when it was created.

Schema uploads and source control

Because schema dumps are the authoritative source for your database schema, it is highly recommended that you include them in source code control.

db/schema.rb contains the number of the current version of the database. This ensures that conflicts arise in the event of a merge of two branches, each of which affected the schema. When this happens, fix the conflicts manually, leaving the highest version number.

(Active Record and Referential Integrity) Active Record and Referential Integrity

Active Record and Referential Integrity

The Active Record way requires the logic to be in the models, not in the database. By and large, features such as triggers or constraints that push some logic back into the database are not actively used.

Validations like validates :foreign_key, uniqueness: true are one way your models can maintain referential integrity. The :dependent option on relationships allows models to automatically destroy child objects when the parent is destroyed. Like anything that works at the application level, this cannot guarantee referential integrity, so someone could add foreign keys as referential integrity delimiters in the database as well.

Although Active Record does not provide any tools to work directly with these functions, the execute method can be used to run arbitrary SQL.

Migrations and seeds

The main purpose of a Rails migration is to run commands that change the schema in a sequential manner. Migrations can also be used to add or change data. This is useful for an existing database that cannot be dropped and recreated, such as a production database.

class AddInitialProducts < ActiveRecord::Migration[5.0]

  def up

    5 times do |i|

      Product.create(name: “Product ##{i}”, description: “A product.”)

    end

  end

  def down

    product.delete_all

  end

end

To add initial data to the database after creation, Rails has a built-in ‘seeds’ feature that makes the process quick and easy. This is especially useful for frequent database reloads in development and test environments. This feature is easy to get started: just populate db/seeds.rb with some Ruby code and run rails db:seed:

5 times do |i|

  Product.create(name: “Product ##{i}”, description: “A product.”)

end

Basically, it’s a cleaner way to set up a database for an empty application.

Leave a Reply

Your email address will not be published.