Skip to content

Database and data migrations

Each ActiveRecord model must have a database table. Database tables are created and modified by migrations.

A migration is a Ruby class that inherits from the current Rails migration version. Schema migrations live in the db/migrate directory of your plugin (for example plugins/easyproject/easy_plugins/modification_acme/db/migrate). Use ActiveRecord::Migration[8.1]. After a Rails upgrade, use the version that rails g migration generates.

Name the class Action + ModelClassName, for example CreateMyModel or AddMyColumnToMyModel. Use a single change method; the up / down format appears only in legacy code.

Filename timestamp format

Migration filenames follow this convention:

YYYYMMDDHHMMSS_migration_class_name.rb

The timestamp is the current UTC time, generated with Time.now.utc.strftime("%Y%m%d%H%M%S") (14 digits). Do not use placeholder timestamps such as 20210101000000.

Example breakdown for 20240502101659:

Part Value Meaning
2024 YYYY Year
05 MM Month
02 DD Day
10 HH Hour (24h, UTC)
16 MM Minute
59 SS Second

Example filename:

db/migrate/20240502101659_create_my_model.rb

Creating a new migration

Use the Rails generator:

rails g migration CreateMyModel author:belongs_to name:string description:text status:integer

The generator writes db/migrate/20240502101659_create_my_model.rb in the application root. Move the file to the db/migrate directory of your plugin. The generated content:

class CreateMyModel < ActiveRecord::Migration[8.1]

  def change
    create_table :my_models do |t|
      t.belongs_to :author, null: false
      t.belongs_to :collection, null: true
      t.string :name, null: false
      t.text :description, null: false
      t.integer :status, null: false, index: true

      t.timestamps
    end
  end

end

Indexing

Columns used for search should be indexed. Define indexes inline:

t.string :name, null: false, index: true

or explicitly with add_index:

add_index :my_models, :name

Currency fields

Register money columns in the model with acts_as_easy_currency. Do not create currency shadow columns such as price_EUR in a migration; the Easy8 install creates them (easyproject:currency_update_tables).

Using an existing table

To reuse an existing table, set self.table_name in the model.

Changing an existing table

Never alter a migration after it has been executed. Always create a new migration for any change. Example:

rails generate migration RemoveDescriptionFromMyModels description:text

Generated migration:

class RemoveDescriptionFromMyModels < ActiveRecord::Migration[8.1]

  def change
    remove_column :my_models, :description, :text
  end

end

Before release

Before the first release of a new model, merge all migrations created during development into a single migration that creates the table with all columns.

While the model changes frequently, use force: true on create_table to drop and recreate the table on each run:

create_table :my_models, force: true do |t|
  # column definitions
end

To re-run such a migration in an already migrated environment, raise its timestamp by one second, for example 20240502101659_create_my_model.rb to 20240502101700_create_my_model.rb.

Data migration

Data migrations change existing data, for example to prepare data before the first release or after a significant change. They live in the db/data directory of your plugin and use the same filename convention as schema migrations.

A data migration inherits from EasyExtensions::EasyDataMigration and implements up and down:

class DataChangeInDb < EasyExtensions::EasyDataMigration

  def up
    # data change implementation
  end

  def down
    # rollback implementation
  end

end

Bulk changes of enabled modules

Project.project_ids_without_module is cached across requests (EasyProjectIdsCache) and kept fresh by the EnabledModule and Project callbacks. A data migration or rake task that changes enabled_modules rows without callbacks (update_all, delete_all, insert_all, raw SQL) must end with Project.invalidate_project_ids_caches, otherwise permission conditions can use the stale list until the next process boot (every boot rotates the cache as a safety net).

Running migrations

db:migrate alone does not run plugin migrations. Run the Easy8 install task, which migrates the core, engines and plugins and runs the data migrations:

bundle exec rails easy8:install