class Sequel::Plugins::InlineMigrations::Migrator
Subclass of Sequel::Migrator that provides the logic for extracting and running migrations from the model classes themselves.
Constants
- DEFAULT_OPTS
Default options for .run and initialize.
Attributes
The Class at the top of the hierarchy from which migrations will be fetched
The name of the column which will contain the names of applied migrations as a Symbol.
The migration table dataset (a Sequel::Dataset).
The database to which the migrator will apply its migrations; a Sequel::Database.
The name of the migration table as a Sequel::SQL::QualifiedIdentifier.
The name of the target migration to play up or down to as a String.
Public Class Methods
Create a new Migrator
that will organize migrations defined for baseclass
or any of its subclasses for the specified db
. See Sequel::Plugins::InlineMigrations::Migrator.run
for argument details.
# File lib/sequel/plugins/inline_migrations.rb, line 308 def initialize( baseclass, db=nil, opts={} ) if db.is_a?( Hash ) opts = db db = nil end db ||= baseclass.db opts = DEFAULT_OPTS.merge( opts ) schema, table = db.send( :schema_and_table, opts[:table] ) @db = db @baseclass = baseclass @table = opts[ :table ] @column = opts[ :column ] @target = opts[ :target ] @dataset = make_schema_dataset( @db, @table, @column ) end
Migrates the supplied db
(a Sequel::Database) using the migrations declared in the given baseclass
. The baseclass
is the class to gather migrations from; it and all of its concrete descendents will be considered.
The options
this method understands:
column : The column in the table that stores the migration version. Defaults to ‘: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 name of the migrations table. Defaults to ‘:schema_migrations`.
target : The target version to migrate to. If not given, migrates to the maximum version.
Examples
“‘ # Assuming Acme::Model is a Sequel::Model subclass, and Acme::Vendor is a subclass # of that… Sequel::InlineMigrations::Migrator.run( Acme::Model ) Sequel::InlineMigrations::Migrator.run( Acme::Model, :target => 15, :current => 10 ) Sequel::InlineMigrations::Migrator.run( Acme::Vendor, :column => :app2_version) Sequel::InlineMigrations::Migrator.run( Acme::Vendor, :column => :app2_version,
:table => :schema_info2 )
“‘
# File lib/sequel/plugins/inline_migrations.rb, line 295 def self::run( baseclass, db=nil, opts={} ) if db.is_a?( Hash ) opts = db db = nil end new( baseclass, db, opts ).run end
Public Instance Methods
Fetch an Array of all model classes which are descended from the migrating subclass, inclusive.
# File lib/sequel/plugins/inline_migrations.rb, line 406 def all_migrating_model_classes return [ self.baseclass ] + self.baseclass.descendents end
Returns any migration objects found in the migrating subclass or any of its descendents as an Array of Sequel::SimpleMigration objects, sorted by the migration name and the name of its migrating class.
# File lib/sequel/plugins/inline_migrations.rb, line 414 def all_migrations migrations = self.all_migrating_model_classes. collect( &:migrations ). compact. inject do |all, hash| all.merge( hash ) do |key, old, new| # rely on the fact that `up` is user defined even for a change block fail "found duplicate `names` for migrations at #{old.up.source_location[0]} and #{new.up.source_location[0]}" end end return migrations.values.sort_by {|m| [m.name, m.model_class.name] } end
Returns two Arrays of migrations, the first one containing those which have already been applied, and the second containing migrations which are pending. Migrations that have been marked as applied but are (no longer) defined by a model class will be ignored.
# File lib/sequel/plugins/inline_migrations.rb, line 433 def get_partitioned_migrations # Get the list of applied migrations for the subclass and its descendents. migrating_class_names = self.all_migrating_model_classes.map( &:name ).compact applied_map = self.dataset. filter( :model_class => migrating_class_names ). select_hash( column, :model_class ) # Split up the migrations by whether or not it exists in the map of applied migrations. # Each one is removed from the map, so it can be checked for consistency part_migrations = self.all_migrations.partition do |migration| applied_map.delete( migration.name ) end # If there are any "applied" migrations left, it's likely been deleted since it was # applied, so just ignore it. unless applied_map.empty? applied_map.each do |migration, classname| db.log_info "No %s migration defined in %s; ignoring it." % [ migration, classname ] end end return part_migrations end
Apply all migrations to the database
# File lib/sequel/plugins/inline_migrations.rb, line 352 def run applied, pending = self.get_partitioned_migrations # If no target was specified, and there are no pending # migrations, return early. return if pending.empty? && self.target.nil? # If no target was specified, the last one is the target target = self.target || pending.last.name migrations = nil direction = nil if target == '0' direction = :down migrations = applied.reverse elsif tgtidx = pending.find_index {|m| m.name == target } migrations = pending[ 0..tgtidx ] direction = :up elsif tgtidx = applied.find_index {|m| m.name == target } migrations = applied[ tgtidx..-1 ].reverse direction = :down else raise Sequel::Error, "couldn't find migration %p" end # Run the selected migrations self.db.log_info "Migrating %d steps %s..." % [ migrations.length, direction ] migrations.each do |migration| start = Time.now self.db.log_info "Begin: %s, direction: %s" % [ migration.description, direction ] self.db.transaction do migration.apply( self.db, direction ) mclass = migration.model_class.name if direction == :up self.dataset.insert( self.column => migration.name, :model_class => mclass ) else self.dataset.filter( self.column => migration.name, :model_class => mclass ).delete end end self.db.log_info " finished: %s, direction: %s (%0.6fs)" % [ migration.description, direction, Time.now - start ] end end
Private Instance Methods
Returns the dataset for the schema_migrations table. If no such table exists, it is automatically created.
# File lib/sequel/plugins/inline_migrations.rb, line 466 def make_schema_dataset( db, table, column ) ds = db.from( table ) db.log_info "Schema dataset is: %p" % [ ds ] if !db.table_exists?( table ) || ds.columns.empty? db.log_info "No migrations table: Installing one." db.create_table( table ) do String column, :primary_key => true String :model_class, :null => false end elsif !ds.columns.include?( column ) raise Sequel::Error, "Migrator table %p does not contain column %p (%p)" % [ table, column, ds.columns ] end return ds end