diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index aba25fb3757ca..0528d17ee7d15 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -10,7 +10,7 @@ module ActiveRecord # # Usage: # - # ActiveRecord::Schema.define do + # ActiveRecord::Schema[7.0].define do # create_table :authors do |t| # t.string :name, null: false # end @@ -30,32 +30,47 @@ module ActiveRecord # ActiveRecord::Schema is only supported by database adapters that also # support migrations, the two features being very similar. 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 ( - # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table], - # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.). - # - # The +info+ hash is optional, and if given is used to define metadata - # about the current schema (currently, only the schema's version): - # - # ActiveRecord::Schema.define(version: 2038_01_19_000001) do - # ... - # end - def self.define(info = {}, &block) - new.define(info, &block) - end + module Definition + extend ActiveSupport::Concern + + module ClassMethods + # 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 ( + # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table], + # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.). + # + # The +info+ hash is optional, and if given is used to define metadata + # about the current schema (currently, only the schema's version): + # + # ActiveRecord::Schema[7.0].define(version: 2038_01_19_000001) do + # ... + # end + def define(info = {}, &block) + new.define(info, &block) + end + end + + def define(info, &block) # :nodoc: + instance_eval(&block) - def define(info, &block) # :nodoc: - instance_eval(&block) + if info[:version].present? + connection.schema_migration.create_table + connection.assume_migrated_upto_version(info[:version]) + end - if info[:version].present? - connection.schema_migration.create_table - connection.assume_migrated_upto_version(info[:version]) + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment end + end + + include Definition - ActiveRecord::InternalMetadata.create_table - ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment + def self.[](version) + @class_for_version ||= {} + @class_for_version[version] ||= Class.new(Migration::Compatibility.find(version)) do + include Definition + end end end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index c97d0899c8313..3db7b29152654 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -74,22 +74,21 @@ def define_params end def header(stream) - stream.puts <
``` +### Rails version is now included in the Active Record schema dump + +Rails 7.0 changed some default values for some column types. To avoid that application upgrading from 6.1 to 7.0 +load the current schema using the new 7.0 defaults, Rails now includes the version of the framework in the schema dump. + +Before loading the schema for the first time in Rails 7.0, make sure to run `rails app:update` to ensure that the +version of the schema is included in the schema dump. + +The schema file will look like this: + +```ruby +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[6.1].define(version: 2022_01_28_123512) do +``` + +NOTE: The first time you dump the schema with Rails 7.0, you will see many changes to that file, including +some column information. Make sure to review the new schema file content and commit it to your repository. + Upgrading from Rails 6.0 to Rails 6.1 ------------------------------------- diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f75b5fae33f87..2a46b689c1146 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -193,6 +193,14 @@ def db directory "db" end + def db_when_updating + path = File.expand_path("db/schema.rb", destination_root) + + if File.exist?(path) + gsub_file("db/schema.rb", /ActiveRecord::Schema\.define/, "ActiveRecord::Schema[6.1].define") + end + end + def lib empty_directory "lib" empty_directory_with_keep_file "lib/tasks" @@ -333,6 +341,11 @@ def update_bin_files end remove_task :update_bin_files + def update_db_schema + build(:db_when_updating) + end + remove_task :update_db_schema + def update_active_storage unless skip_active_storage? rails_command "active_storage:update", inline: true diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index ff240359be4fa..dc8743ff8dd21 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -2,7 +2,7 @@ namespace :app do desc "Update configs and some other initially generated files (or use just update:configs or update:bin)" - task update: [ "update:configs", "update:bin", "update:active_storage", "update:upgrade_guide_info" ] + task update: [ "update:configs", "update:bin", "update:db", "update:active_storage", "update:upgrade_guide_info" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" task template: :environment do @@ -51,6 +51,10 @@ namespace :app do Rails::AppUpdater.invoke_from_app_generator :update_bin_files end + task :db do + Rails::AppUpdater.invoke_from_app_generator :update_db_schema + end + task :active_storage do Rails::AppUpdater.invoke_from_app_generator :update_active_storage end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c0e1b979ab6ba..567142c404840 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -230,6 +230,60 @@ def test_app_update_does_not_remove_rack_cors_if_already_present end end + def test_app_update_set_the_schema_version_to_6_1 + app_root = File.join(destination_root, "myapp") + run_generator [app_root] + + File.write("#{app_root}/db/schema.rb", <<~RUBY) + ActiveRecord::Schema.define(version: 1) do + create_table :users do |t| + t.string :name + end + end + RUBY + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.update_db_schema } + assert_file "#{app_root}/db/schema.rb", /ActiveRecord::Schema\[6\.1\]\.define\(version: 1\)/ + end + end + + def test_app_update_set_the_schema_version_to_6_1_if_already_set + app_root = File.join(destination_root, "myapp") + run_generator [app_root] + + File.write("#{app_root}/db/schema.rb", <<~RUBY) + ActiveRecord::Schema[7.0].define(version: 1) do + create_table :users do |t| + t.string :name + end + end + RUBY + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.update_db_schema } + assert_file "#{app_root}/db/schema.rb", /ActiveRecord::Schema\[7\.0\]\.define\(version: 1\)/ + end + end + + def test_app_update_set_the_schema_version_if_structe_file_is_used + app_root = File.join(destination_root, "myapp") + run_generator [app_root] + + assert_no_file "#{app_root}/db/schema.rb" + + stub_rails_application(app_root) do + generator = Rails::Generators::AppGenerator.new ["rails"], [], destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.update_db_schema } + assert_no_file "#{app_root}/db/schema.rb" + end + end + def test_app_update_does_not_generate_assets_initializer_when_sprockets_is_not_used app_root = File.join(destination_root, "myapp") run_generator [app_root, "-a", "none"] @@ -276,17 +330,6 @@ def test_app_update_does_not_generate_bootsnap_contents_when_skip_bootsnap_is_gi end end - def test_gem_for_active_storage - run_generator - assert_file "Gemfile", /^# gem "image_processing"/ - end - - def test_gem_for_active_storage_when_skip_active_storage_is_given - run_generator [destination_root, "--skip-active-storage"] - - assert_no_gem "image_processing" - end - def test_app_update_does_not_generate_active_storage_contents_when_skip_active_storage_is_given app_root = File.join(destination_root, "myapp") run_generator [app_root, "--skip-active-storage"] @@ -337,6 +380,17 @@ def test_app_update_does_not_generate_active_storage_contents_when_skip_active_r end end + def test_gem_for_active_storage + run_generator + assert_file "Gemfile", /^# gem "image_processing"/ + end + + def test_gem_for_active_storage_when_skip_active_storage_is_given + run_generator [destination_root, "--skip-active-storage"] + + assert_no_gem "image_processing" + end + def test_generator_skips_action_mailbox_when_skip_action_mailbox_is_given run_generator [destination_root, "--skip-action-mailbox"] assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/