From 6940dc860c4b25bff2eded370f2af4316de15a30 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 6 Dec 2015 06:07:12 +1030 Subject: Add migration versioning via Migration subclasses --- activerecord/lib/active_record/migration.rb | 26 +++++++++++++++++++ .../lib/active_record/migration/compatibility.rb | 30 ++++++++++++++++++++++ .../migration/templates/create_table_migration.rb | 2 +- .../active_record/migration/templates/migration.rb | 2 +- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 activerecord/lib/active_record/migration/compatibility.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index ca2537cdc3..53a84c1342 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -476,6 +476,32 @@ module ActiveRecord # are in a Migration with self.disable_ddl_transaction!. class Migration autoload :CommandRecorder, 'active_record/migration/command_recorder' + autoload :Compatibility, 'active_record/migration/compatibility' + + # This must be defined before the inherited hook, below + class Current < Migration # :nodoc: + end + + def self.inherited(subclass) # :nodoc: + super + if subclass.superclass == Migration + subclass.include Compatibility::Legacy + end + end + + def self.[](version) + version = version.to_s + name = "V#{version.tr('.', '_')}" + unless Compatibility.const_defined?(name) + versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect } + raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + end + Compatibility.const_get(name) + end + + def self.current_version + Rails.version.to_f + end MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc: diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb new file mode 100644 index 0000000000..71d702e812 --- /dev/null +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -0,0 +1,30 @@ +module ActiveRecord + class Migration + module Compatibility # :nodoc: all + V5_0 = Current + + module FourTwoShared + end + + class V4_2 < V5_0 + # 4.2 is defined as a module because it needs to be shared with + # Legacy. When the time comes, V5_0 should be defined straight + # in its class. + include FourTwoShared + end + + module Legacy + include FourTwoShared + + def run(*) + ActiveSupport::Deprecation.warn \ + "Directly inheriting from ActiveRecord::Migration is deprecated. " \ + "Please specify the Rails release the migration was written for:\n" \ + "\n" \ + " class #{self.class.name} < ActiveRecord::Migration[4.2]" + super + end + end + end + end +end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index fadab2a1e6..5f7201cfe1 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb @@ -1,4 +1,4 @@ -class <%= migration_class_name %> < ActiveRecord::Migration +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] def change create_table :<%= table_name %><%= primary_key_type %> do |t| <% attributes.each do |attribute| -%> diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index 23a377db6a..107f107dc4 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -1,4 +1,4 @@ -class <%= migration_class_name %> < ActiveRecord::Migration +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> -- cgit v1.2.3 From c0af95e0abe92ea676bf73959ea84d2fccc8c8fb Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 6 Dec 2015 06:29:19 +1030 Subject: Use a deliberately-invalid migration version in all doc examples If we use a real version, at best that'll be an onerous update required for each release; at worst, it will encourage users to write new migrations against an older version than they're using. The other option would be to leave these bare, without any version specifier. But as that's just a variant spelling of "4.2", it would seem to raise the same concerns as above. --- activerecord/lib/active_record/associations.rb | 2 +- .../abstract/schema_definitions.rb | 2 +- activerecord/lib/active_record/migration.rb | 30 +++++++++++----------- activerecord/lib/active_record/model_schema.rb | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index b806a2f832..f2a637e727 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1639,7 +1639,7 @@ module ActiveRecord # The join table should not have a primary key or a model associated with it. You must manually generate the # join table with a migration such as this: # - # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[0.0] # def change # create_join_table :developers, :projects # end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 159cbcb85a..9ec8e0fc26 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -190,7 +190,7 @@ module ActiveRecord # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table] # is actually of this type: # - # class SomeMigration < ActiveRecord::Migration + # class SomeMigration < ActiveRecord::Migration[0.0] # def up # create_table :foo do |t| # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 53a84c1342..b2418e4247 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -13,7 +13,7 @@ module ActiveRecord # For example the following migration is not reversible. # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error. # - # class IrreversibleMigrationExample < ActiveRecord::Migration + # class IrreversibleMigrationExample < ActiveRecord::Migration[0.0] # def change # create_table :distributors do |t| # t.string :zipcode @@ -31,7 +31,7 @@ module ActiveRecord # # 1. Define #up and #down methods instead of #change: # - # class ReversibleMigrationExample < ActiveRecord::Migration + # class ReversibleMigrationExample < ActiveRecord::Migration[0.0] # def up # create_table :distributors do |t| # t.string :zipcode @@ -56,7 +56,7 @@ module ActiveRecord # # 2. Use the #reversible method in #change method: # - # class ReversibleMigrationExample < ActiveRecord::Migration + # class ReversibleMigrationExample < ActiveRecord::Migration[0.0] # def change # create_table :distributors do |t| # t.string :zipcode @@ -155,7 +155,7 @@ module ActiveRecord # # Example of a simple migration: # - # class AddSsl < ActiveRecord::Migration + # class AddSsl < ActiveRecord::Migration[0.0] # def up # add_column :accounts, :ssl_enabled, :boolean, default: true # end @@ -175,7 +175,7 @@ module ActiveRecord # # Example of a more complex migration that also needs to initialize data: # - # class AddSystemSettings < ActiveRecord::Migration + # class AddSystemSettings < ActiveRecord::Migration[0.0] # def up # create_table :system_settings do |t| # t.string :name @@ -301,7 +301,7 @@ module ActiveRecord # rails generate migration add_fieldname_to_tablename fieldname:string # # This will generate the file timestamp_add_fieldname_to_tablename.rb, which will look like this: - # class AddFieldnameToTablename < ActiveRecord::Migration + # class AddFieldnameToTablename < ActiveRecord::Migration[0.0] # def change # add_column :tablenames, :fieldname, :string # end @@ -332,7 +332,7 @@ module ActiveRecord # # Not all migrations change the schema. Some just fix the data: # - # class RemoveEmptyTags < ActiveRecord::Migration + # class RemoveEmptyTags < ActiveRecord::Migration[0.0] # def up # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end @@ -345,7 +345,7 @@ module ActiveRecord # # Others remove columns when they migrate up instead of down: # - # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[0.0] # def up # remove_column :items, :incomplete_items_count # remove_column :items, :completed_items_count @@ -359,7 +359,7 @@ module ActiveRecord # # And sometimes you need to do something in SQL not abstracted directly by migrations: # - # class MakeJoinUnique < ActiveRecord::Migration + # class MakeJoinUnique < ActiveRecord::Migration[0.0] # def up # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" # end @@ -376,7 +376,7 @@ module ActiveRecord # 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 + # class AddPeopleSalary < ActiveRecord::Migration[0.0] # def up # add_column :people, :salary, :integer # Person.reset_column_information @@ -434,7 +434,7 @@ module ActiveRecord # To define a reversible migration, define the +change+ method in your # migration like this: # - # class TenderloveMigration < ActiveRecord::Migration + # class TenderloveMigration < ActiveRecord::Migration[0.0] # def change # create_table(:horses) do |t| # t.column :content, :text @@ -464,7 +464,7 @@ module ActiveRecord # can't execute inside a transaction though, and for these situations # you can turn the automatic transactions off. # - # class ChangeEnum < ActiveRecord::Migration + # class ChangeEnum < ActiveRecord::Migration[0.0] # disable_ddl_transaction! # # def up @@ -601,7 +601,7 @@ module ActiveRecord # and create the table 'apples' on the way up, and the reverse # on the way down. # - # class FixTLMigration < ActiveRecord::Migration + # class FixTLMigration < ActiveRecord::Migration[0.0] # def change # revert do # create_table(:horses) do |t| @@ -620,7 +620,7 @@ module ActiveRecord # # require_relative '20121212123456_tenderlove_migration' # - # class FixupTLMigration < ActiveRecord::Migration + # class FixupTLMigration < ActiveRecord::Migration[0.0] # def change # revert TenderloveMigration # @@ -673,7 +673,7 @@ module ActiveRecord # when the three columns 'first_name', 'last_name' and 'full_name' exist, # even when migrating down: # - # class SplitNameMigration < ActiveRecord::Migration + # class SplitNameMigration < ActiveRecord::Migration[0.0] # def change # add_column :users, :first_name, :string # add_column :users, :last_name, :string diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index e3f304b0af..8ae2a61aa8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -275,7 +275,7 @@ module ActiveRecord # when just after creating a table you want to populate it with some default # values, eg: # - # class CreateJobLevels < ActiveRecord::Migration + # class CreateJobLevels < ActiveRecord::Migration[0.0] # def up # create_table :job_levels do |t| # t.integer :id -- cgit v1.2.3 From f37d92c41036d4eff168b8a8951a8b8a76baa347 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 6 Dec 2015 07:20:07 +1030 Subject: Find the delegate, even in a deeper inheritance tree --- activerecord/lib/active_record/migration.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b2418e4247..d7244d1df7 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -535,6 +535,10 @@ module ActiveRecord attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: + def nearest_delegate # :nodoc: + delegate || superclass.nearest_delegate + end + # Raises ActiveRecord::PendingMigrationError error if any migrations are pending. def check_pending!(connection = Base.connection) raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) @@ -561,7 +565,7 @@ module ActiveRecord end def method_missing(name, *args, &block) # :nodoc: - (delegate || superclass.delegate).send(name, *args, &block) + nearest_delegate.send(name, *args, &block) end def migrate(direction) -- cgit v1.2.3 From badaf353ba4591ce78fa0c305b656f7cae56992b Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 6 Dec 2015 06:09:00 +1030 Subject: In 4.2 migrations, `timestamps` defaulted to `null: true` .. it also showed a deprecation warning, but we obviously needn't retain that. --- .../lib/active_record/migration/compatibility.rb | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 71d702e812..4c8db8a2d5 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -4,6 +4,30 @@ module ActiveRecord V5_0 = Current module FourTwoShared + module TableDefinition + def timestamps(*, **options) + options[:null] = true if options[:null].nil? + super + end + end + + def create_table(table_name, options = {}) + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + + def add_timestamps(*, **options) + options[:null] = true if options[:null].nil? + super + end end class V4_2 < V5_0 -- cgit v1.2.3 From 6d2469daa6b29ce5113e1855df5e23cba32c24e8 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 6 Dec 2015 08:09:30 +1030 Subject: Schema uses current migration API --- activerecord/lib/active_record/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 31dd584538..fdf9965a82 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -27,7 +27,7 @@ module ActiveRecord # # ActiveRecord::Schema is only supported by database adapters that also # support migrations, the two features being very similar. - class Schema < Migration + class Schema < Migration::Current # Eval the given block. All methods available to the current connection # adapter are available within the block, so you can easily use the # database definition DSL to build up your schema ( -- cgit v1.2.3 From 97c77160cd4c8b32a6572adefa268eb91162e3e1 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Tue, 15 Dec 2015 17:10:40 +1030 Subject: Use a real migration version number in docs Even though this means more things to change when we bump after a release, it's more important that our examples are directly copyable. --- activerecord/lib/active_record/associations.rb | 2 +- .../abstract/schema_definitions.rb | 2 +- activerecord/lib/active_record/migration.rb | 30 +++++++++++----------- activerecord/lib/active_record/model_schema.rb | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f2a637e727..04ad45f5da 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1639,7 +1639,7 @@ module ActiveRecord # The join table should not have a primary key or a model associated with it. You must manually generate the # join table with a migration such as this: # - # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[0.0] + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] # def change # create_join_table :developers, :projects # end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 9ec8e0fc26..1cda23dc1d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -190,7 +190,7 @@ module ActiveRecord # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table] # is actually of this type: # - # class SomeMigration < ActiveRecord::Migration[0.0] + # class SomeMigration < ActiveRecord::Migration[5.0] # def up # create_table :foo do |t| # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index d7244d1df7..43726d795e 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -13,7 +13,7 @@ module ActiveRecord # For example the following migration is not reversible. # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error. # - # class IrreversibleMigrationExample < ActiveRecord::Migration[0.0] + # class IrreversibleMigrationExample < ActiveRecord::Migration[5.0] # def change # create_table :distributors do |t| # t.string :zipcode @@ -31,7 +31,7 @@ module ActiveRecord # # 1. Define #up and #down methods instead of #change: # - # class ReversibleMigrationExample < ActiveRecord::Migration[0.0] + # class ReversibleMigrationExample < ActiveRecord::Migration[5.0] # def up # create_table :distributors do |t| # t.string :zipcode @@ -56,7 +56,7 @@ module ActiveRecord # # 2. Use the #reversible method in #change method: # - # class ReversibleMigrationExample < ActiveRecord::Migration[0.0] + # class ReversibleMigrationExample < ActiveRecord::Migration[5.0] # def change # create_table :distributors do |t| # t.string :zipcode @@ -155,7 +155,7 @@ module ActiveRecord # # Example of a simple migration: # - # class AddSsl < ActiveRecord::Migration[0.0] + # class AddSsl < ActiveRecord::Migration[5.0] # def up # add_column :accounts, :ssl_enabled, :boolean, default: true # end @@ -175,7 +175,7 @@ module ActiveRecord # # Example of a more complex migration that also needs to initialize data: # - # class AddSystemSettings < ActiveRecord::Migration[0.0] + # class AddSystemSettings < ActiveRecord::Migration[5.0] # def up # create_table :system_settings do |t| # t.string :name @@ -301,7 +301,7 @@ module ActiveRecord # rails generate migration add_fieldname_to_tablename fieldname:string # # This will generate the file timestamp_add_fieldname_to_tablename.rb, which will look like this: - # class AddFieldnameToTablename < ActiveRecord::Migration[0.0] + # class AddFieldnameToTablename < ActiveRecord::Migration[5.0] # def change # add_column :tablenames, :fieldname, :string # end @@ -332,7 +332,7 @@ module ActiveRecord # # Not all migrations change the schema. Some just fix the data: # - # class RemoveEmptyTags < ActiveRecord::Migration[0.0] + # class RemoveEmptyTags < ActiveRecord::Migration[5.0] # def up # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } # end @@ -345,7 +345,7 @@ module ActiveRecord # # Others remove columns when they migrate up instead of down: # - # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[0.0] + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[5.0] # def up # remove_column :items, :incomplete_items_count # remove_column :items, :completed_items_count @@ -359,7 +359,7 @@ module ActiveRecord # # And sometimes you need to do something in SQL not abstracted directly by migrations: # - # class MakeJoinUnique < ActiveRecord::Migration[0.0] + # class MakeJoinUnique < ActiveRecord::Migration[5.0] # def up # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" # end @@ -376,7 +376,7 @@ module ActiveRecord # 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[0.0] + # class AddPeopleSalary < ActiveRecord::Migration[5.0] # def up # add_column :people, :salary, :integer # Person.reset_column_information @@ -434,7 +434,7 @@ module ActiveRecord # To define a reversible migration, define the +change+ method in your # migration like this: # - # class TenderloveMigration < ActiveRecord::Migration[0.0] + # class TenderloveMigration < ActiveRecord::Migration[5.0] # def change # create_table(:horses) do |t| # t.column :content, :text @@ -464,7 +464,7 @@ module ActiveRecord # can't execute inside a transaction though, and for these situations # you can turn the automatic transactions off. # - # class ChangeEnum < ActiveRecord::Migration[0.0] + # class ChangeEnum < ActiveRecord::Migration[5.0] # disable_ddl_transaction! # # def up @@ -605,7 +605,7 @@ module ActiveRecord # and create the table 'apples' on the way up, and the reverse # on the way down. # - # class FixTLMigration < ActiveRecord::Migration[0.0] + # class FixTLMigration < ActiveRecord::Migration[5.0] # def change # revert do # create_table(:horses) do |t| @@ -624,7 +624,7 @@ module ActiveRecord # # require_relative '20121212123456_tenderlove_migration' # - # class FixupTLMigration < ActiveRecord::Migration[0.0] + # class FixupTLMigration < ActiveRecord::Migration[5.0] # def change # revert TenderloveMigration # @@ -677,7 +677,7 @@ module ActiveRecord # when the three columns 'first_name', 'last_name' and 'full_name' exist, # even when migrating down: # - # class SplitNameMigration < ActiveRecord::Migration[0.0] + # class SplitNameMigration < ActiveRecord::Migration[5.0] # def change # add_column :users, :first_name, :string # add_column :users, :last_name, :string diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 8ae2a61aa8..5df67cdbe7 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -275,7 +275,7 @@ module ActiveRecord # when just after creating a table you want to populate it with some default # values, eg: # - # class CreateJobLevels < ActiveRecord::Migration[0.0] + # class CreateJobLevels < ActiveRecord::Migration[5.0] # def up # create_table :job_levels do |t| # t.integer :id -- cgit v1.2.3