~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activerecord/lib/active_record/migration.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
module ActiveRecord
2
 
  class IrreversibleMigration < ActiveRecordError#:nodoc:
3
 
  end
4
 
 
5
 
  class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
6
 
    def initialize(version)
7
 
      super("Multiple migrations have the version number #{version}")
8
 
    end
9
 
  end
10
 
 
11
 
  class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
12
 
    def initialize(name)
13
 
      super("Multiple migrations have the name #{name}")
14
 
    end
15
 
  end
16
 
 
17
 
  class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
18
 
    def initialize(version)
19
 
      super("No migration with version number #{version}")
20
 
    end
21
 
  end
22
 
 
23
 
  class IllegalMigrationNameError < ActiveRecordError#:nodoc:
24
 
    def initialize(name)
25
 
      super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
26
 
    end
27
 
  end
28
 
 
29
 
  # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
30
 
  # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
31
 
  # push that change to other developers and to the production server. With migrations, you can describe the transformations
32
 
  # in self-contained classes that can be checked into version control systems and executed against another database that
33
 
  # might be one, two, or five versions behind.
34
 
  #
35
 
  # Example of a simple migration:
36
 
  #
37
 
  #   class AddSsl < ActiveRecord::Migration
38
 
  #     def self.up
39
 
  #       add_column :accounts, :ssl_enabled, :boolean, :default => 1
40
 
  #     end
41
 
  #
42
 
  #     def self.down
43
 
  #       remove_column :accounts, :ssl_enabled
44
 
  #     end
45
 
  #   end
46
 
  #
47
 
  # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
48
 
  # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
49
 
  # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
50
 
  # but may also contain regular Ruby code for generating data needed for the transformations.
51
 
  #
52
 
  # Example of a more complex migration that also needs to initialize data:
53
 
  #
54
 
  #   class AddSystemSettings < ActiveRecord::Migration
55
 
  #     def self.up
56
 
  #       create_table :system_settings do |t|
57
 
  #         t.string  :name
58
 
  #         t.string  :label
59
 
  #         t.text  :value
60
 
  #         t.string  :type
61
 
  #         t.integer  :position
62
 
  #       end
63
 
  #
64
 
  #       SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
65
 
  #     end
66
 
  #
67
 
  #     def self.down
68
 
  #       drop_table :system_settings
69
 
  #     end
70
 
  #   end
71
 
  #
72
 
  # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
73
 
  # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
74
 
  # in one block call.
75
 
  #
76
 
  # == Available transformations
77
 
  #
78
 
  # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
79
 
  #   that can then add columns to it, following the same format as add_column. See example above. The options hash is for
80
 
  #   fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
81
 
  # * <tt>drop_table(name)</tt>: Drops the table called +name+.
82
 
  # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
83
 
  # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
84
 
  #   named +column_name+ specified to be one of the following types:
85
 
  #   <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
86
 
  #   <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be specified by passing an
87
 
  #   +options+ hash like <tt>{ :default => 11 }</tt>. Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g. <tt>{ :limit => 50, :null => false }</tt>)
88
 
  #   -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
89
 
  # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
90
 
  # * <tt>change_column(table_name, column_name, type, options)</tt>:  Changes the column to a different type using the same
91
 
  #   parameters as add_column.
92
 
  # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
93
 
  # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
94
 
  #   <tt>:name</tt> and <tt>:unique</tt> (e.g. <tt>{ :name => "users_name_index", :unique => true }</tt>).
95
 
  # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
96
 
  #
97
 
  # == Irreversible transformations
98
 
  #
99
 
  # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
100
 
  # an <tt>ActiveRecord::IrreversibleMigration</tt> exception in their +down+ method.
101
 
  #
102
 
  # == Running migrations from within Rails
103
 
  #
104
 
  # The Rails package has several tools to help create and apply migrations.
105
 
  #
106
 
  # To generate a new migration, you can use 
107
 
  #   script/generate migration MyNewMigration
108
 
  #
109
 
  # where MyNewMigration is the name of your migration. The generator will
110
 
  # create an empty migration file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
111
 
  # directory where <tt>nnn</tt> is the next largest migration number.
112
 
  #
113
 
  # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
114
 
  # MyNewMigration.
115
 
  #
116
 
  # There is a special syntactic shortcut to generate migrations that add fields to a table.
117
 
  #   script/generate migration add_fieldname_to_tablename fieldname:string
118
 
  #
119
 
  # This will generate the file <tt>nnn_add_fieldname_to_tablename</tt>, which will look like this:
120
 
  #   class AddFieldnameToTablename < ActiveRecord::Migration
121
 
  #     def self.up
122
 
  #       add_column :tablenames, :fieldname, :string
123
 
  #     end
124
 
  # 
125
 
  #     def self.down
126
 
  #       remove_column :tablenames, :fieldname
127
 
  #     end
128
 
  #   end
129
 
  # 
130
 
  # To run migrations against the currently configured database, use
131
 
  # <tt>rake db:migrate</tt>. This will update the database by running all of the
132
 
  # pending migrations, creating the <tt>schema_migrations</tt> table
133
 
  # (see "About the schema_migrations table" section below) if missing. It will also 
134
 
  # invoke the db:schema:dump task, which will update your db/schema.rb file
135
 
  # to match the structure of your database.
136
 
  #
137
 
  # To roll the database back to a previous migration version, use
138
 
  # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
139
 
  # you wish to downgrade. If any of the migrations throw an
140
 
  # <tt>ActiveRecord::IrreversibleMigration</tt> exception, that step will fail and you'll
141
 
  # have some manual work to do.
142
 
  #
143
 
  # == Database support
144
 
  #
145
 
  # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
146
 
  # SQL Server, Sybase, and Oracle (all supported databases except DB2).
147
 
  #
148
 
  # == More examples
149
 
  #
150
 
  # Not all migrations change the schema. Some just fix the data:
151
 
  #
152
 
  #   class RemoveEmptyTags < ActiveRecord::Migration
153
 
  #     def self.up
154
 
  #       Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
155
 
  #     end
156
 
  #
157
 
  #     def self.down
158
 
  #       # not much we can do to restore deleted data
159
 
  #       raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
160
 
  #     end
161
 
  #   end
162
 
  #
163
 
  # Others remove columns when they migrate up instead of down:
164
 
  #
165
 
  #   class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
166
 
  #     def self.up
167
 
  #       remove_column :items, :incomplete_items_count
168
 
  #       remove_column :items, :completed_items_count
169
 
  #     end
170
 
  #
171
 
  #     def self.down
172
 
  #       add_column :items, :incomplete_items_count
173
 
  #       add_column :items, :completed_items_count
174
 
  #     end
175
 
  #   end
176
 
  #
177
 
  # And sometimes you need to do something in SQL not abstracted directly by migrations:
178
 
  #
179
 
  #   class MakeJoinUnique < ActiveRecord::Migration
180
 
  #     def self.up
181
 
  #       execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
182
 
  #     end
183
 
  #
184
 
  #     def self.down
185
 
  #       execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
186
 
  #     end
187
 
  #   end
188
 
  #
189
 
  # == Using a model after changing its table
190
 
  #
191
 
  # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
192
 
  # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
193
 
  # after the new column was added. Example:
194
 
  #
195
 
  #   class AddPeopleSalary < ActiveRecord::Migration
196
 
  #     def self.up
197
 
  #       add_column :people, :salary, :integer
198
 
  #       Person.reset_column_information
199
 
  #       Person.find(:all).each do |p|
200
 
  #         p.update_attribute :salary, SalaryCalculator.compute(p)
201
 
  #       end
202
 
  #     end
203
 
  #   end
204
 
  #
205
 
  # == Controlling verbosity
206
 
  #
207
 
  # By default, migrations will describe the actions they are taking, writing
208
 
  # them to the console as they happen, along with benchmarks describing how
209
 
  # long each step took.
210
 
  #
211
 
  # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
212
 
  #
213
 
  # You can also insert your own messages and benchmarks by using the +say_with_time+
214
 
  # method:
215
 
  #
216
 
  #   def self.up
217
 
  #     ...
218
 
  #     say_with_time "Updating salaries..." do
219
 
  #       Person.find(:all).each do |p|
220
 
  #         p.update_attribute :salary, SalaryCalculator.compute(p)
221
 
  #       end
222
 
  #     end
223
 
  #     ...
224
 
  #   end
225
 
  #
226
 
  # The phrase "Updating salaries..." would then be printed, along with the
227
 
  # benchmark for the block when the block completes.
228
 
  #
229
 
  # == About the schema_migrations table
230
 
  #
231
 
  # Rails versions 2.0 and prior used to create a table called
232
 
  # <tt>schema_info</tt> when using migrations. This table contained the
233
 
  # version of the schema as of the last applied migration.
234
 
  #
235
 
  # Starting with Rails 2.1, the <tt>schema_info</tt> table is
236
 
  # (automatically) replaced by the <tt>schema_migrations</tt> table, which
237
 
  # contains the version numbers of all the migrations applied.
238
 
  #
239
 
  # As a result, it is now possible to add migration files that are numbered
240
 
  # lower than the current schema version: when migrating up, those
241
 
  # never-applied "interleaved" migrations will be automatically applied, and
242
 
  # when migrating down, never-applied "interleaved" migrations will be skipped.
243
 
  # 
244
 
  # == Timestamped Migrations
245
 
  #
246
 
  # By default, Rails generates migrations that look like:
247
 
  #
248
 
  #    20080717013526_your_migration_name.rb
249
 
  #
250
 
  # The prefix is a generation timestamp (in UTC).
251
 
  #
252
 
  # If you'd prefer to use numeric prefixes, you can turn timestamped migrations
253
 
  # off by setting:
254
 
  #
255
 
  #    config.active_record.timestamped_migrations = false
256
 
  # 
257
 
  # In environment.rb.
258
 
  #
259
 
  class Migration
260
 
    @@verbose = true
261
 
    cattr_accessor :verbose
262
 
 
263
 
    class << self
264
 
      def up_with_benchmarks #:nodoc:
265
 
        migrate(:up)
266
 
      end
267
 
 
268
 
      def down_with_benchmarks #:nodoc:
269
 
        migrate(:down)
270
 
      end
271
 
 
272
 
      # Execute this migration in the named direction
273
 
      def migrate(direction)
274
 
        return unless respond_to?(direction)
275
 
 
276
 
        case direction
277
 
          when :up   then announce "migrating"
278
 
          when :down then announce "reverting"
279
 
        end
280
 
 
281
 
        result = nil
282
 
        time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
283
 
 
284
 
        case direction
285
 
          when :up   then announce "migrated (%.4fs)" % time.real; write
286
 
          when :down then announce "reverted (%.4fs)" % time.real; write
287
 
        end
288
 
 
289
 
        result
290
 
      end
291
 
 
292
 
      # Because the method added may do an alias_method, it can be invoked
293
 
      # recursively. We use @ignore_new_methods as a guard to indicate whether
294
 
      # it is safe for the call to proceed.
295
 
      def singleton_method_added(sym) #:nodoc:
296
 
        return if defined?(@ignore_new_methods) && @ignore_new_methods
297
 
 
298
 
        begin
299
 
          @ignore_new_methods = true
300
 
 
301
 
          case sym
302
 
            when :up, :down
303
 
              klass = (class << self; self; end)
304
 
              klass.send(:alias_method_chain, sym, "benchmarks")
305
 
          end
306
 
        ensure
307
 
          @ignore_new_methods = false
308
 
        end
309
 
      end
310
 
 
311
 
      def write(text="")
312
 
        puts(text) if verbose
313
 
      end
314
 
 
315
 
      def announce(message)
316
 
        text = "#{@version} #{name}: #{message}"
317
 
        length = [0, 75 - text.length].max
318
 
        write "== %s %s" % [text, "=" * length]
319
 
      end
320
 
 
321
 
      def say(message, subitem=false)
322
 
        write "#{subitem ? "   ->" : "--"} #{message}"
323
 
      end
324
 
 
325
 
      def say_with_time(message)
326
 
        say(message)
327
 
        result = nil
328
 
        time = Benchmark.measure { result = yield }
329
 
        say "%.4fs" % time.real, :subitem
330
 
        say("#{result} rows", :subitem) if result.is_a?(Integer)
331
 
        result
332
 
      end
333
 
 
334
 
      def suppress_messages
335
 
        save, self.verbose = verbose, false
336
 
        yield
337
 
      ensure
338
 
        self.verbose = save
339
 
      end
340
 
 
341
 
      def connection
342
 
        ActiveRecord::Base.connection
343
 
      end
344
 
 
345
 
      def method_missing(method, *arguments, &block)
346
 
        arg_list = arguments.map(&:inspect) * ', '
347
 
 
348
 
        say_with_time "#{method}(#{arg_list})" do
349
 
          unless arguments.empty? || method == :execute
350
 
            arguments[0] = Migrator.proper_table_name(arguments.first)
351
 
          end
352
 
          connection.send(method, *arguments, &block)
353
 
        end
354
 
      end
355
 
    end
356
 
  end
357
 
 
358
 
  # MigrationProxy is used to defer loading of the actual migration classes
359
 
  # until they are needed
360
 
  class MigrationProxy
361
 
 
362
 
    attr_accessor :name, :version, :filename
363
 
 
364
 
    delegate :migrate, :announce, :write, :to=>:migration
365
 
 
366
 
    private
367
 
 
368
 
      def migration
369
 
        @migration ||= load_migration
370
 
      end
371
 
 
372
 
      def load_migration
373
 
        load(filename)
374
 
        name.constantize
375
 
      end
376
 
 
377
 
  end
378
 
 
379
 
  class Migrator#:nodoc:
380
 
    class << self
381
 
      def migrate(migrations_path, target_version = nil)
382
 
        case
383
 
          when target_version.nil?              then up(migrations_path, target_version)
384
 
          when current_version > target_version then down(migrations_path, target_version)
385
 
          else                                       up(migrations_path, target_version)
386
 
        end
387
 
      end
388
 
 
389
 
      def rollback(migrations_path, steps=1)
390
 
        migrator = self.new(:down, migrations_path)
391
 
        start_index = migrator.migrations.index(migrator.current_migration)
392
 
        
393
 
        return unless start_index
394
 
        
395
 
        finish = migrator.migrations[start_index + steps]
396
 
        down(migrations_path, finish ? finish.version : 0)
397
 
      end
398
 
 
399
 
      def up(migrations_path, target_version = nil)
400
 
        self.new(:up, migrations_path, target_version).migrate
401
 
      end
402
 
 
403
 
      def down(migrations_path, target_version = nil)
404
 
        self.new(:down, migrations_path, target_version).migrate
405
 
      end
406
 
      
407
 
      def run(direction, migrations_path, target_version)
408
 
        self.new(direction, migrations_path, target_version).run
409
 
      end
410
 
 
411
 
      def schema_migrations_table_name
412
 
        Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
413
 
      end
414
 
 
415
 
      def get_all_versions
416
 
        Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
417
 
      end
418
 
 
419
 
      def current_version
420
 
        sm_table = schema_migrations_table_name
421
 
        if Base.connection.table_exists?(sm_table)
422
 
          get_all_versions.max || 0
423
 
        else
424
 
          0
425
 
        end
426
 
      end
427
 
 
428
 
      def proper_table_name(name)
429
 
        # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
430
 
        name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
431
 
      end
432
 
    end
433
 
 
434
 
    def initialize(direction, migrations_path, target_version = nil)
435
 
      raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
436
 
      Base.connection.initialize_schema_migrations_table
437
 
      @direction, @migrations_path, @target_version = direction, migrations_path, target_version      
438
 
    end
439
 
 
440
 
    def current_version
441
 
      migrated.last || 0
442
 
    end
443
 
    
444
 
    def current_migration
445
 
      migrations.detect { |m| m.version == current_version }
446
 
    end
447
 
    
448
 
    def run
449
 
      target = migrations.detect { |m| m.version == @target_version }
450
 
      raise UnknownMigrationVersionError.new(@target_version) if target.nil?
451
 
      unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
452
 
        target.migrate(@direction)
453
 
        record_version_state_after_migrating(target.version)
454
 
      end
455
 
    end
456
 
 
457
 
    def migrate
458
 
      current = migrations.detect { |m| m.version == current_version }
459
 
      target = migrations.detect { |m| m.version == @target_version }
460
 
 
461
 
      if target.nil? && !@target_version.nil? && @target_version > 0
462
 
        raise UnknownMigrationVersionError.new(@target_version)
463
 
      end
464
 
      
465
 
      start = up? ? 0 : (migrations.index(current) || 0)
466
 
      finish = migrations.index(target) || migrations.size - 1
467
 
      runnable = migrations[start..finish]
468
 
      
469
 
      # skip the last migration if we're headed down, but not ALL the way down
470
 
      runnable.pop if down? && !target.nil?
471
 
      
472
 
      runnable.each do |migration|
473
 
        Base.logger.info "Migrating to #{migration.name} (#{migration.version})"
474
 
 
475
 
        # On our way up, we skip migrating the ones we've already migrated
476
 
        next if up? && migrated.include?(migration.version.to_i)
477
 
 
478
 
        # On our way down, we skip reverting the ones we've never migrated
479
 
        if down? && !migrated.include?(migration.version.to_i)
480
 
          migration.announce 'never migrated, skipping'; migration.write
481
 
          next
482
 
        end
483
 
 
484
 
        begin
485
 
          ddl_transaction do
486
 
            migration.migrate(@direction)
487
 
            record_version_state_after_migrating(migration.version)
488
 
          end
489
 
        rescue => e
490
 
          canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
491
 
          raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
492
 
        end
493
 
      end
494
 
    end
495
 
 
496
 
    def migrations
497
 
      @migrations ||= begin
498
 
        files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
499
 
        
500
 
        migrations = files.inject([]) do |klasses, file|
501
 
          version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
502
 
          
503
 
          raise IllegalMigrationNameError.new(file) unless version
504
 
          version = version.to_i
505
 
          
506
 
          if klasses.detect { |m| m.version == version }
507
 
            raise DuplicateMigrationVersionError.new(version) 
508
 
          end
509
 
 
510
 
          if klasses.detect { |m| m.name == name.camelize }
511
 
            raise DuplicateMigrationNameError.new(name.camelize) 
512
 
          end
513
 
          
514
 
          klasses << returning(MigrationProxy.new) do |migration|
515
 
            migration.name     = name.camelize
516
 
            migration.version  = version
517
 
            migration.filename = file
518
 
          end
519
 
        end
520
 
        
521
 
        migrations = migrations.sort_by(&:version)
522
 
        down? ? migrations.reverse : migrations
523
 
      end
524
 
    end
525
 
 
526
 
    def pending_migrations
527
 
      already_migrated = migrated
528
 
      migrations.reject { |m| already_migrated.include?(m.version.to_i) }
529
 
    end
530
 
 
531
 
    def migrated
532
 
      @migrated_versions ||= self.class.get_all_versions
533
 
    end
534
 
 
535
 
    private
536
 
      def record_version_state_after_migrating(version)
537
 
        sm_table = self.class.schema_migrations_table_name
538
 
 
539
 
        @migrated_versions ||= []
540
 
        if down?
541
 
          @migrated_versions.delete(version.to_i)
542
 
          Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
543
 
        else
544
 
          @migrated_versions.push(version.to_i).sort!
545
 
          Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
546
 
        end
547
 
      end
548
 
 
549
 
      def up?
550
 
        @direction == :up
551
 
      end
552
 
 
553
 
      def down?
554
 
        @direction == :down
555
 
      end
556
 
 
557
 
      # Wrap the migration in a transaction only if supported by the adapter.
558
 
      def ddl_transaction(&block)
559
 
        if Base.connection.supports_ddl_transactions?
560
 
          Base.transaction { block.call }
561
 
        else
562
 
          block.call
563
 
        end
564
 
      end
565
 
  end
566
 
end