Index Migrations#
CouchbaseOrm supports versioned index migrations inspired by ActiveRecord. Index migrations let you evolve N1QL indexes over time and keep the index definition history in your application code.
Configuration#
Rails applications can configure index migration options in
config/couchbase.yml using a nested index section:
development:
connection_string: couchbase://localhost
username: dev_user
password: dev_password
bucket: fleet-dev
index:
migrations_path: custom/indexes
schema_path: custom/index_schema.rb
num_replica: 1
The index bucket defaults to the top-level bucket unless index.bucket
is set explicitly.
You can also configure or override index migration options in Ruby with
CouchbaseOrm.configure:
CouchbaseOrm.configure do |config|
config.index.bucket = 'fleet-prod'
config.index.migrations_path = 'db/indexes'
config.index.schema_path = 'db/index_schema.rb'
config.index.num_replica = 1
end
Supported options:
Option |
Default |
Description |
|---|---|---|
|
top-level connection bucket in Rails, otherwise |
Bucket used in generated |
|
|
Directory used to load and generate index migration files. |
|
|
Path used to read and write the index schema snapshot. |
|
|
Value used in the index |
In Rails, values are applied in this order:
Default index configuration values.
The top-level connection
bucketfromconfig/couchbase.yml.Nested
indexvalues fromconfig/couchbase.yml.Later explicit
CouchbaseOrm.configurecalls.
Creating Migrations#
Generate a new migration file:
bundle exec couchbaseorm index:generate AddWorkflowIndex
This creates a timestamped file in db/indexes:
db/indexes/20250808130000_add_workflow_index.rb
Template:
class AddWorkflowIndex < CouchbaseOrm::IndexMigration
def change
end
end
DSL#
add_index creates an index:
Immediate index example:
add_index(
:type_company,
keys: [:type, :company_id],
where: 'type is valued and company_id is valued'
)
Deferred index example:
add_index(
:type_company,
keys: [:type, :company_id],
where: 'type is valued and company_id is valued',
defer_build: true
)
Index names can be provided as symbols or strings.
Use strings for non-conventional names (for example names containing -):
add_index(
'type-company',
keys: [:type, :company_id]
)
The same rule applies to remove_index and rename_index.
Example generated query:
CREATE INDEX `type_company`
ON `fleet-prod`(`type`,`company_id`)
WHERE (type is valued and company_id is valued)
WITH {
"defer_build": true,
"num_replica": 1
}
build_indexes explicitly builds one or more deferred indexes:
build_indexes :type_company, :date_on_type
To wait until all built indexes are online, pass wait: true:
build_indexes :type_company, :date_on_type, wait: true
Example generated query:
BUILD INDEX ON `fleet-prod`
(
`type_company`,
`date_on_type`
);
When wait: true is used, CouchbaseOrm polls system:indexes after the
BUILD INDEX statement and resumes when every requested index reports
state = online.
Polling query shape:
SELECT name, state
FROM system:indexes
WHERE keyspace_id = 'fleet-prod'
AND name IN ['type_company', 'date_on_type']
Polling runs every second and does not apply a timeout.
remove_index drops an index:
remove_index :type_company
For names containing -, pass a string:
remove_index 'type-company'
Example generated query:
DROP INDEX `fleet-prod`.`type_company`
Reversible behavior#
For simple migrations, define change:
class InitialIndexes < CouchbaseOrm::IndexMigration
def change
add_index :type_company, keys: [:type, :company_id]
end
end
Rollback automatically applies the inverse for supported operations.
add_index is reversible and rolls back with remove_index.
build_indexes is irreversible. If it is used inside change, rollback
raises CouchbaseOrm::IndexMigration::IrreversibleMigration.
If a migration cannot be reversed from change alone (for example when
calling remove_index or build_indexes), define explicit up and
down methods.
class ChangeFleetByCompanyIndex < CouchbaseOrm::IndexMigration
def up
remove_index :fleet_by_company
add_index :fleet_by_company, keys: [:type, :company_id]
end
def down
remove_index :fleet_by_company
add_index :fleet_by_company, keys: [:company_id, :type]
end
end
Execution Example#
Deferred index creation and explicit build operations execute in order:
class InitialIndexes < CouchbaseOrm::IndexMigration
def up
add_index :type_company,
keys: [:type, :company_id],
where: 'type is valued and company_id is valued',
defer_build: true
add_index :date_on_type,
keys: [:date],
where: 'type is valued and date is valued',
defer_build: true
build_indexes :type_company, :date_on_type
end
def down
remove_index :date_on_type
remove_index :type_company
end
end
Generated operations:
CREATE INDEX `type_company`
ON `fleet-prod`(`type`,`company_id`)
WHERE (type is valued and company_id is valued)
WITH {
"defer_build": true,
"num_replica": 1
}
CREATE INDEX `date_on_type`
ON `fleet-prod`(`date`)
WHERE (type is valued and date is valued)
WITH {
"defer_build": true,
"num_replica": 1
}
BUILD INDEX ON `fleet-prod`
(
`type_company`,
`date_on_type`
);
Safe replacement flows can wait for readiness before removing old indexes:
class ChangeFleetByCompanyIndex < CouchbaseOrm::IndexMigration
def up
add_index :fleet_by_company_v2,
keys: [:type, :company_id],
defer_build: true
build_indexes :fleet_by_company_v2, wait: true
remove_index :fleet_by_company
end
end
No internal tracking of deferred indexes is performed; migrations must call
build_indexes explicitly when builds are required.
Running Migrations#
Run pending migrations:
bundle exec couchbaseorm index:migrate
Rollback the latest applied migration:
bundle exec couchbaseorm index:rollback
Show migration status:
bundle exec couchbaseorm index:status
Schema dump and load:
bundle exec couchbaseorm index:schema:dump
bundle exec couchbaseorm index:schema:load
index:schema:dump replays migrations in memory and writes the current index
state to db/index_schema.rb (or index.schema_path when configured).
index:schema:load applies indexes from the schema file directly, without
replaying migration files.
During schema load, indexes declared with defer_build: true are collected
and built automatically in a single BUILD INDEX statement.
Example status output:
up 20250808110000 InitialIndexes
up 20250808120000 AddWorkflowIndex
down 20250808130000 ChangeFleetByCompanyIndex
Migration State#
Applied versions are stored in the Couchbase document
couchbaseorm::index_schema_migrations as:
{
"versions": [
"20250808110000",
"20250808120000"
]
}