Module: Sequel::Migrator

Defined in:
lib/sequel/lib/sequel/extensions/migration.rb

Overview

The Migrator module performs migrations based on migration files in a specified directory. The migration files should be named using the following pattern (in similar fashion to ActiveRecord migrations):

<version>_<title>.rb

For example, the following files are considered migration files:

001_create_sessions.rb
002_add_data_column.rb
...

The migration files should contain one or more migration classes based on Sequel::Migration.

Migrations are generally run via the sequel command line tool, using the -m and -M switches. The -m switch specifies the migration directory, and the -M switch specifies the version to which to migrate.

You can apply migrations using the Migrator API, as well (this is necessary if you want to specify the version from which to migrate in addition to the version to which to migrate). To apply a migration, the #apply method must be invoked with the database instance, the directory of migration files and the target version. If no current version is supplied, it is read from the database. The migrator automatically creates a schema_info table in the database to keep track of the current migration version. If no migration version is stored in the database, the version is considered to be 0. If no target version is specified, the database is migrated to the latest version available in the migration directory.

For example, to migrate the database to the latest version:

Sequel::Migrator.apply(DB, '.')

To migrate the database from version 1 to version 5:

Sequel::Migrator.apply(DB, '.', 5, 1)

Constant Summary collapse

DEFAULT_SCHEMA_COLUMN =
:version
DEFAULT_SCHEMA_TABLE =
:schema_info
MIGRATION_FILE_PATTERN =
/\A\d+_.+\.rb\z/.freeze
MIGRATION_SPLITTER =
'_'.freeze

Class Method Summary collapse

Class Method Details

.apply(db, directory, target = nil, current = nil) ⇒ Object

Wrapper for run, maintaining backwards API compatibility


140
141
142
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 140

def self.apply(db, directory, target = nil, current = nil)
  run(db, directory, :target => target, :current => current)
end

.get_current_migration_version(db, opts = {}) ⇒ Object

Gets the current migration version stored in the database. If no version number is stored, 0 is returned.


175
176
177
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 175

def self.get_current_migration_version(db, opts={})
  (schema_info_dataset(db, opts).first || {})[opts[:column] || DEFAULT_SCHEMA_COLUMN] || 0
end

.latest_migration_version(directory) ⇒ Object

Returns the latest version available in the specified directory.


180
181
182
183
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 180

def self.latest_migration_version(directory)
  l = migration_files(directory).last
  l ? migration_version_from_file(File.basename(l)) : nil
end

.migration_classes(directory, target, current, direction) ⇒ Object

Returns a list of migration classes filtered for the migration range and ordered according to the migration direction.


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 187

def self.migration_classes(directory, target, current, direction)
  range = direction == :up ?
    (current + 1)..target : (target + 1)..current

  # Remove class definitions
  Migration.descendants.each do |c|
    Object.send(:remove_const, c.to_s) rescue nil
  end
  Migration.descendants.clear # remove any defined migration classes

  # load migration files
  migration_files(directory, range).each {|fn| load(fn)}
  
  # get migration classes
  classes = Migration.descendants
  classes.reverse! if direction == :down
  classes
end

.migration_files(directory, range = nil) ⇒ Object

Returns any found migration files in the supplied directory.


207
208
209
210
211
212
213
214
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 207

def self.migration_files(directory, range = nil)
  files = []
  Dir.new(directory).each do |file|
    files[migration_version_from_file(file)] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
  end
  filtered = range ? files[range] : files
  filtered ? filtered.compact : []
end

.run(db, directory, opts = {}) ⇒ Object

Migrates the supplied database using the migration files in the the specified directory. Options:

  • :column - The column in the :table argument storing the migration version (default: :version).

  • :current - The current version of the database. If not given, it is retrieved from the database using the :table and :column options.

  • :table - The table containing the schema version (default: :schema_info).

  • :target - The target version to which to migrate. If not given, migrates to the maximum version.

Examples:

Sequel::Migrator.run(DB, "migrations")
Sequel::Migrator.run(DB, "migrations", :target=>15, :current=>10)
Sequel::Migrator.run(DB, "app1/migrations", :column=> :app2_version)
Sequel::Migrator.run(DB, "app2/migrations", :column => :app2_version, :table=>:schema_info2)

Raises:


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 156

def self.run(db, directory, opts={})
  raise(Error, "Must supply a valid migration path") unless directory and File.directory?(directory)
  raise(Error, "No current version available") unless current = opts[:current] || get_current_migration_version(db, opts)
  raise(Error, "No target version available") unless target  = opts[:target]  || latest_migration_version(directory)

  direction = current < target ? :up : :down
  
  classes = migration_classes(directory, target, current, direction)

  db.transaction do
    classes.each {|c| c.apply(db, direction)}
    set_current_migration_version(db, target, opts)
  end
  
  target
end

.schema_info_dataset(db, opts = {}) ⇒ Object

Returns the dataset for the schema_info table. If no such table exists, it is automatically created.


218
219
220
221
222
223
224
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 218

def self.schema_info_dataset(db, opts={})
  column = opts[:column] || DEFAULT_SCHEMA_COLUMN
  table  = opts[:table]  || DEFAULT_SCHEMA_TABLE
  db.create_table?(table){Integer column}
  db.alter_table(table){add_column column, Integer} unless db.from(table).columns.include?(column)
  db.from(table)
end

.set_current_migration_version(db, version, opts = {}) ⇒ Object

Sets the current migration version stored in the database.


227
228
229
230
231
# File 'lib/sequel/lib/sequel/extensions/migration.rb', line 227

def self.set_current_migration_version(db, version, opts={})
  column = opts[:column] || DEFAULT_SCHEMA_COLUMN
  dataset = schema_info_dataset(db, opts)
  dataset.send(dataset.first ? :update : :insert, column => version)
end