doc/development/database/batched_background_migration_spec_helpers.md
The versioned spec helper library for batched background migrations reduces boilerplate code in migration specs.
Batched background migration specs often require defining multiple table helpers using
the table() method from MigrationsHelpers. This results in repetitive code:
# Without helpers - repetitive
RSpec.describe Gitlab::BackgroundMigration::BackfillProjectId do
let!(:projects) { table(:projects) }
let!(:issues) { table(:issues) }
let!(:notes) { table(:notes) }
let!(:users) { table(:users) }
# ... more table definitions
end
The batched background migration spec helpers eliminate this repetition through lazy evaluation and memoization:
# With helpers - clean and concise
RSpec.describe Gitlab::BackgroundMigration::BackfillProjectId do
include Gitlab::BackgroundMigration::SpecHelpers::V1
# Declare the tables you need
tables :projects, :issues, :notes, :users
end
The helpers are versioned to ensure backward compatibility. When modifications are needed, a new version can be created without breaking existing specs.
Include the version module you want to use:
RSpec.describe Gitlab::BackgroundMigration::SomeMigration do
include Gitlab::BackgroundMigration::SpecHelpers::V1
end
Tables must be explicitly declared using the tables method before they can be accessed:
RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceId do
include Gitlab::BackgroundMigration::SpecHelpers::V1
# Declare all tables needed in your spec
tables :issues, :namespaces, :projects
it 'backfills namespace_id' do
# Tables are created on first access after declaration
namespace = namespaces.create!(name: 'test', path: 'test')
project = projects.create!(namespace_id: namespace.id)
issue = issues.create!(project_id: project.id)
end
end
Table helpers are memoized, so repeated access returns the same instance:
RSpec.describe Gitlab::BackgroundMigration::SomeMigration do
include Gitlab::BackgroundMigration::SpecHelpers::V1
tables :users
it 'uses memoized tables' do
users_1 = users # Creates the helper on first access
users_2 = users # Returns the same instance
expect(users_1).to be(users_2)
end
end
You can configure tables with custom options:
RSpec.describe Gitlab::BackgroundMigration::SomeMigration do
include Gitlab::BackgroundMigration::SpecHelpers::V1
tables :custom_table
configure_table :custom_table, primary_key: :custom_id
it 'uses custom primary key' do
record = custom_table.create!(custom_id: 1, name: 'test')
expect(custom_table.primary_key).to eq('custom_id')
end
end
RSpec.describe Gitlab::BackgroundMigration::SomeMigration do
include Gitlab::BackgroundMigration::SpecHelpers::V1
tables :ci_builds
configure_table :ci_builds, database: :ci
end
RSpec.describe Gitlab::BackgroundMigration::SomeMigration, migration: :gitlab_ci do
include Gitlab::BackgroundMigration::SpecHelpers::V1
tables :p_ci_builds
configure_table :p_ci_builds, partitioned: true, database: :ci
it 'works with partitioned tables' do
build = p_ci_builds.create!(partition_id: 100, project_id: 1)
end
end
To convert an existing spec to use the helpers:
Before:
RSpec.describe Gitlab::BackgroundMigration::BackfillProjectId,
feature_category: :code_review_workflow do
let!(:projects) { table(:projects) }
let!(:merge_requests) { table(:merge_requests) }
let!(:approval_rules) { table(:approval_merge_request_rules) }
it 'backfills project_id' do
project = projects.create!(name: 'test')
mr = merge_requests.create!(target_project_id: project.id)
rule = approval_rules.create!(merge_request_id: mr.id)
# test logic
end
end
After:
RSpec.describe Gitlab::BackgroundMigration::BackfillProjectId,
feature_category: :code_review_workflow do
include Gitlab::BackgroundMigration::SpecHelpers::V1
tables :approval_merge_request_rules, :merge_requests, :projects
it 'backfills project_id' do
project = projects.create!(name: 'test')
mr = merge_requests.create!(target_project_id: project.id)
rule = approval_merge_request_rules.create!(merge_request_id: mr.id)
# test logic
end
end
Use the helpers when:
Consider manual definitions when:
tables method to declare all tables needed in your spec. Tables are not automatically available and must be declared before use.tables first, then configure them with configure_table if needed.let! definitions, not both in the same spec.If you get a "table not found" error, ensure:
tables :table_name at the class level:main, :ci, etc.)If you have primary key errors:
configure_table :table_name, primary_key: :custom_idFor partitioned tables:
migration: :gitlab_ciconfigure_table :p_ci_builds, partitioned: truedatabase: :ciWhen new versions are released, this document is updated with: