X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fmigration.rb;fp=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fmigration.rb;h=1d843fff2842ecf20ffa6497e577bfd1bba56283;hb=d115f2e23823271635bad69229a42cd8ac68debe;hp=0000000000000000000000000000000000000000;hpb=37cb670bf3ddde90b214e591f100ed4446469484;p=depot.git diff --git a/vendor/rails/activerecord/lib/active_record/migration.rb b/vendor/rails/activerecord/lib/active_record/migration.rb new file mode 100644 index 0000000..1d843ff --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/migration.rb @@ -0,0 +1,560 @@ +module ActiveRecord + class IrreversibleMigration < ActiveRecordError#:nodoc: + end + + class DuplicateMigrationVersionError < ActiveRecordError#:nodoc: + def initialize(version) + super("Multiple migrations have the version number #{version}") + end + end + + class DuplicateMigrationNameError < ActiveRecordError#:nodoc: + def initialize(name) + super("Multiple migrations have the name #{name}") + end + end + + class UnknownMigrationVersionError < ActiveRecordError #:nodoc: + def initialize(version) + super("No migration with version number #{version}") + end + end + + class IllegalMigrationNameError < ActiveRecordError#:nodoc: + def initialize(name) + super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)") + end + end + + # Migrations can manage the evolution of a schema used by several physical databases. It's a solution + # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to + # push that change to other developers and to the production server. With migrations, you can describe the transformations + # in self-contained classes that can be checked into version control systems and executed against another database that + # might be one, two, or five versions behind. + # + # Example of a simple migration: + # + # class AddSsl < ActiveRecord::Migration + # def self.up + # add_column :accounts, :ssl_enabled, :boolean, :default => 1 + # end + # + # def self.down + # remove_column :accounts, :ssl_enabled + # end + # end + # + # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration. + # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement + # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column, + # but may also contain regular Ruby code for generating data needed for the transformations. + # + # Example of a more complex migration that also needs to initialize data: + # + # class AddSystemSettings < ActiveRecord::Migration + # def self.up + # create_table :system_settings do |t| + # t.string :name + # t.string :label + # t.text :value + # t.string :type + # t.integer :position + # end + # + # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 + # end + # + # def self.down + # drop_table :system_settings + # end + # end + # + # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model + # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema + # in one block call. + # + # == Available transformations + # + # * create_table(name, options) Creates a table called +name+ and makes the table object available to a block + # that can then add columns to it, following the same format as add_column. See example above. The options hash is for + # fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition. + # * drop_table(name): Drops the table called +name+. + # * rename_table(old_name, new_name): Renames the table called +old_name+ to +new_name+. + # * add_column(table_name, column_name, type, options): Adds a new column to the table called +table_name+ + # named +column_name+ specified to be one of the following types: + # :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, + # :date, :binary, :boolean. A default value can be specified by passing an + # +options+ hash like { :default => 11 }. Other options include :limit and :null (e.g. { :limit => 50, :null => false }) + # -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details. + # * rename_column(table_name, column_name, new_column_name): Renames a column but keeps the type and content. + # * change_column(table_name, column_name, type, options): Changes the column to a different type using the same + # parameters as add_column. + # * remove_column(table_name, column_name): Removes the column named +column_name+ from the table called +table_name+. + # * add_index(table_name, column_names, options): Adds a new index with the name of the column. Other options include + # :name and :unique (e.g. { :name => "users_name_index", :unique => true }). + # * remove_index(table_name, index_name): Removes the index specified by +index_name+. + # + # == Irreversible transformations + # + # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise + # an ActiveRecord::IrreversibleMigration exception in their +down+ method. + # + # == Running migrations from within Rails + # + # The Rails package has several tools to help create and apply migrations. + # + # To generate a new migration, you can use + # script/generate migration MyNewMigration + # + # where MyNewMigration is the name of your migration. The generator will + # create an empty migration file nnn_my_new_migration.rb in the db/migrate/ + # directory where nnn is the next largest migration number. + # + # You may then edit the self.up and self.down methods of + # MyNewMigration. + # + # There is a special syntactic shortcut to generate migrations that add fields to a table. + # script/generate migration add_fieldname_to_tablename fieldname:string + # + # This will generate the file nnn_add_fieldname_to_tablename, which will look like this: + # class AddFieldnameToTablename < ActiveRecord::Migration + # def self.up + # add_column :tablenames, :fieldname, :string + # end + # + # def self.down + # remove_column :tablenames, :fieldname + # end + # end + # + # To run migrations against the currently configured database, use + # rake db:migrate. This will update the database by running all of the + # pending migrations, creating the schema_migrations table + # (see "About the schema_migrations table" section below) if missing. + # + # To roll the database back to a previous migration version, use + # rake db:migrate VERSION=X where X is the version to which + # you wish to downgrade. If any of the migrations throw an + # ActiveRecord::IrreversibleMigration exception, that step will fail and you'll + # have some manual work to do. + # + # == Database support + # + # Migrations are currently supported in MySQL, PostgreSQL, SQLite, + # SQL Server, Sybase, and Oracle (all supported databases except DB2). + # + # == More examples + # + # Not all migrations change the schema. Some just fix the data: + # + # class RemoveEmptyTags < ActiveRecord::Migration + # def self.up + # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? } + # end + # + # def self.down + # # not much we can do to restore deleted data + # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags" + # end + # end + # + # Others remove columns when they migrate up instead of down: + # + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration + # def self.up + # remove_column :items, :incomplete_items_count + # remove_column :items, :completed_items_count + # end + # + # def self.down + # add_column :items, :incomplete_items_count + # add_column :items, :completed_items_count + # end + # end + # + # And sometimes you need to do something in SQL not abstracted directly by migrations: + # + # class MakeJoinUnique < ActiveRecord::Migration + # def self.up + # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" + # end + # + # def self.down + # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`" + # end + # end + # + # == Using a model after changing its table + # + # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need + # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from + # after the new column was added. Example: + # + # class AddPeopleSalary < ActiveRecord::Migration + # def self.up + # add_column :people, :salary, :integer + # Person.reset_column_information + # Person.find(:all).each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # end + # + # == Controlling verbosity + # + # By default, migrations will describe the actions they are taking, writing + # them to the console as they happen, along with benchmarks describing how + # long each step took. + # + # You can quiet them down by setting ActiveRecord::Migration.verbose = false. + # + # You can also insert your own messages and benchmarks by using the +say_with_time+ + # method: + # + # def self.up + # ... + # say_with_time "Updating salaries..." do + # Person.find(:all).each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # ... + # end + # + # The phrase "Updating salaries..." would then be printed, along with the + # benchmark for the block when the block completes. + # + # == About the schema_migrations table + # + # Rails versions 2.0 and prior used to create a table called + # schema_info when using migrations. This table contained the + # version of the schema as of the last applied migration. + # + # Starting with Rails 2.1, the schema_info table is + # (automatically) replaced by the schema_migrations table, which + # contains the version numbers of all the migrations applied. + # + # As a result, it is now possible to add migration files that are numbered + # lower than the current schema version: when migrating up, those + # never-applied "interleaved" migrations will be automatically applied, and + # when migrating down, never-applied "interleaved" migrations will be skipped. + # + # == Timestamped Migrations + # + # By default, Rails generates migrations that look like: + # + # 20080717013526_your_migration_name.rb + # + # The prefix is a generation timestamp (in UTC). + # + # If you'd prefer to use numeric prefixes, you can turn timestamped migrations + # off by setting: + # + # config.active_record.timestamped_migrations = false + # + # In environment.rb. + # + class Migration + @@verbose = true + cattr_accessor :verbose + + class << self + def up_with_benchmarks #:nodoc: + migrate(:up) + end + + def down_with_benchmarks #:nodoc: + migrate(:down) + end + + # Execute this migration in the named direction + def migrate(direction) + return unless respond_to?(direction) + + case direction + when :up then announce "migrating" + when :down then announce "reverting" + end + + result = nil + time = Benchmark.measure { result = send("#{direction}_without_benchmarks") } + + case direction + when :up then announce "migrated (%.4fs)" % time.real; write + when :down then announce "reverted (%.4fs)" % time.real; write + end + + result + end + + # Because the method added may do an alias_method, it can be invoked + # recursively. We use @ignore_new_methods as a guard to indicate whether + # it is safe for the call to proceed. + def singleton_method_added(sym) #:nodoc: + return if defined?(@ignore_new_methods) && @ignore_new_methods + + begin + @ignore_new_methods = true + + case sym + when :up, :down + klass = (class << self; self; end) + klass.send(:alias_method_chain, sym, "benchmarks") + end + ensure + @ignore_new_methods = false + end + end + + def write(text="") + puts(text) if verbose + end + + def announce(message) + text = "#{@version} #{name}: #{message}" + length = [0, 75 - text.length].max + write "== %s %s" % [text, "=" * length] + end + + def say(message, subitem=false) + write "#{subitem ? " ->" : "--"} #{message}" + end + + def say_with_time(message) + say(message) + result = nil + time = Benchmark.measure { result = yield } + say "%.4fs" % time.real, :subitem + say("#{result} rows", :subitem) if result.is_a?(Integer) + result + end + + def suppress_messages + save, self.verbose = verbose, false + yield + ensure + self.verbose = save + end + + def method_missing(method, *arguments, &block) + arg_list = arguments.map(&:inspect) * ', ' + + say_with_time "#{method}(#{arg_list})" do + unless arguments.empty? || method == :execute + arguments[0] = Migrator.proper_table_name(arguments.first) + end + ActiveRecord::Base.connection.send(method, *arguments, &block) + end + end + end + end + + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + class MigrationProxy + + attr_accessor :name, :version, :filename + + delegate :migrate, :announce, :write, :to=>:migration + + private + + def migration + @migration ||= load_migration + end + + def load_migration + load(filename) + name.constantize + end + + end + + class Migrator#:nodoc: + class << self + def migrate(migrations_path, target_version = nil) + case + when target_version.nil? then up(migrations_path, target_version) + when current_version > target_version then down(migrations_path, target_version) + else up(migrations_path, target_version) + end + end + + def rollback(migrations_path, steps=1) + migrator = self.new(:down, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + return unless start_index + + finish = migrator.migrations[start_index + steps] + down(migrations_path, finish ? finish.version : 0) + end + + def up(migrations_path, target_version = nil) + self.new(:up, migrations_path, target_version).migrate + end + + def down(migrations_path, target_version = nil) + self.new(:down, migrations_path, target_version).migrate + end + + def run(direction, migrations_path, target_version) + self.new(direction, migrations_path, target_version).run + end + + def schema_migrations_table_name + Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix + end + + def get_all_versions + Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort + end + + def current_version + sm_table = schema_migrations_table_name + if Base.connection.table_exists?(sm_table) + get_all_versions.max || 0 + else + 0 + end + end + + def proper_table_name(name) + # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string + name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" + end + end + + def initialize(direction, migrations_path, target_version = nil) + raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? + Base.connection.initialize_schema_migrations_table + @direction, @migrations_path, @target_version = direction, migrations_path, target_version + end + + def current_version + migrated.last || 0 + end + + def current_migration + migrations.detect { |m| m.version == current_version } + end + + def run + target = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if target.nil? + unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i)) + target.migrate(@direction) + record_version_state_after_migrating(target.version) + end + end + + def migrate + current = migrations.detect { |m| m.version == current_version } + target = migrations.detect { |m| m.version == @target_version } + + if target.nil? && !@target_version.nil? && @target_version > 0 + raise UnknownMigrationVersionError.new(@target_version) + end + + start = up? ? 0 : (migrations.index(current) || 0) + finish = migrations.index(target) || migrations.size - 1 + runnable = migrations[start..finish] + + # skip the last migration if we're headed down, but not ALL the way down + runnable.pop if down? && !target.nil? + + runnable.each do |migration| + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" + + # On our way up, we skip migrating the ones we've already migrated + next if up? && migrated.include?(migration.version.to_i) + + # On our way down, we skip reverting the ones we've never migrated + if down? && !migrated.include?(migration.version.to_i) + migration.announce 'never migrated, skipping'; migration.write + next + end + + begin + ddl_transaction do + migration.migrate(@direction) + record_version_state_after_migrating(migration.version) + end + rescue => e + canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : "" + raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace + end + end + end + + def migrations + @migrations ||= begin + files = Dir["#{@migrations_path}/[0-9]*_*.rb"] + + migrations = files.inject([]) do |klasses, file| + version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first + + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + + if klasses.detect { |m| m.version == version } + raise DuplicateMigrationVersionError.new(version) + end + + if klasses.detect { |m| m.name == name.camelize } + raise DuplicateMigrationNameError.new(name.camelize) + end + + klasses << returning(MigrationProxy.new) do |migration| + migration.name = name.camelize + migration.version = version + migration.filename = file + end + end + + migrations = migrations.sort_by(&:version) + down? ? migrations.reverse : migrations + end + end + + def pending_migrations + already_migrated = migrated + migrations.reject { |m| already_migrated.include?(m.version.to_i) } + end + + def migrated + @migrated_versions ||= self.class.get_all_versions + end + + private + def record_version_state_after_migrating(version) + sm_table = self.class.schema_migrations_table_name + + @migrated_versions ||= [] + if down? + @migrated_versions.delete(version.to_i) + Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") + else + @migrated_versions.push(version.to_i).sort! + Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") + end + end + + def up? + @direction == :up + end + + def down? + @direction == :down + end + + # Wrap the migration in a transaction only if supported by the adapter. + def ddl_transaction(&block) + if Base.connection.supports_ddl_transactions? + Base.transaction { block.call } + else + block.call + end + end + end +end