From 0e20cb885d803c11952ff7691da2b8f74642eb1e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Mon, 13 Nov 2023 15:50:05 +0800 Subject: [PATCH] * Update db:migrate:up & db:migrate:down to act more like active record one --- lib/sequel_rails/migrations.rb | 56 +++++- lib/sequel_rails/railties/database.rake | 4 +- .../railties/database_rake_spec.rb | 166 ++++++++++++++++++ spec/spec_helper.rb | 2 + 4 files changed, 223 insertions(+), 5 deletions(-) diff --git a/lib/sequel_rails/migrations.rb b/lib/sequel_rails/migrations.rb index 90fb471e..e29db1c2 100644 --- a/lib/sequel_rails/migrations.rb +++ b/lib/sequel_rails/migrations.rb @@ -3,13 +3,12 @@ module SequelRails class Migrations class << self - def migrate(version = nil) - opts = {} + def migrate(version = nil, migrator_class = ::Sequel::Migrator, opts = {}) opts[:target] = version.to_i if version opts[:allow_missing_migration_files] = !!SequelRails.configuration.allow_missing_migration_files if migrations_dir.directory? - ::Sequel::Migrator.run(::Sequel::Model.db, migrations_dir, opts) + migrator_class.run(::Sequel::Model.db, migrations_dir, opts) else relative_path_name = migrations_dir.relative_path_from(Rails.root).to_s raise "The #{relative_path_name} directory doesn't exist, you need to create it." @@ -18,6 +17,14 @@ def migrate(version = nil) alias_method :migrate_up!, :migrate alias_method :migrate_down!, :migrate + def migrate_up_one_version!(version) + migrate(version, SequelRails::OneTimestampVersionMigrator, { direction: :up }) + end + + def migrate_down_one_version!(version) + migrate(version, SequelRails::OneTimestampVersionMigrator, { direction: :down }) + end + def pending_migrations? return false unless available_migrations? !::Sequel::Migrator.is_current?(::Sequel::Model.db, migrations_dir) @@ -78,4 +85,47 @@ def init_migrator end end end + + class OneTimestampVersionMigrator < ::Sequel::TimestampMigrator + Error = ::Sequel::Migrator::Error + + # The direction of the migrator, either :up or :down + attr_reader :direction + + # Set up all state for the migrator instance + def initialize(db, directory, opts = OPTS) + @direction = opts[:direction] + super + + raise(Error, 'target (version) is absent') if target.nil? + raise(Error, "Invalid direction: #{direction}") unless [:up, :down].include?(direction) + # `allow_missing_migration_files` ignored, as version is from user input + raise(Error, "Migration with version #{target} not found") if migration_tuples.empty? + end + + private + + def get_migration_tuples + up_mts = [] + down_mts = [] + files.each do |path| + f = File.basename(path) + fi = f.downcase + next unless migration_version_from_file(f) == target + + case [direction, applied_migrations.include?(fi)] + when [:up, false] + # Normal migrate up + up_mts << [load_migration_file(path), f, :up] + when [:down, true] + # Normal migrate down + down_mts << [load_migration_file(path), f, :down] + else + # Already run, don't re-run + raise(Error, "Migration with version #{target} is already migrated/rollback") + end + end + up_mts + down_mts.reverse + end + end end diff --git a/lib/sequel_rails/railties/database.rake b/lib/sequel_rails/railties/database.rake index 9b6b2ab6..b5b59921 100644 --- a/lib/sequel_rails/railties/database.rake +++ b/lib/sequel_rails/railties/database.rake @@ -154,7 +154,7 @@ namespace sequel_rails_namespace do task :up => :load do version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required' unless version - SequelRails::Migrations.migrate_up!(version) + SequelRails::Migrations.migrate_up_one_version!(version) Rake::Task["#{sequel_rails_namespace}:dump"].invoke if SequelRails.configuration.schema_dump end @@ -162,7 +162,7 @@ namespace sequel_rails_namespace do task :down => :load do version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required' unless version - SequelRails::Migrations.migrate_down!(version) + SequelRails::Migrations.migrate_down_one_version!(version) Rake::Task["#{sequel_rails_namespace}:dump"].invoke if SequelRails.configuration.schema_dump end end diff --git a/spec/lib/sequel_rails/railties/database_rake_spec.rb b/spec/lib/sequel_rails/railties/database_rake_spec.rb index f01a38dc..da1f883c 100644 --- a/spec/lib/sequel_rails/railties/database_rake_spec.rb +++ b/spec/lib/sequel_rails/railties/database_rake_spec.rb @@ -81,6 +81,172 @@ end end + describe 'db:migrate:up' do + let(:version) { nil } + around do |example| + env_value_before = ENV['VERSION'] + ENV['VERSION'] = version + example.run + ENV['VERSION'] = env_value_before + end + let(:rake_task_call) do + proc { Rake::Task['db:migrate:up'].execute } + end + + context 'when no version supplied' do + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error('VERSION is required') + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'when version with no matching migration supplied' do + let(:version) { '1273253848' } + + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error("Migration with version #{version} not found") + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'when version with matching migration supplied' do + let(:version) { '1273253849' } + + context 'and migration already run' do + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error("Migration with version #{version} is already migrated/rollback") + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'and migration NOT already run' do + before do + Dir.chdir app_root do + SequelRails::Migrations.migrate_down!(0) + end + end + + it 'runs the matching migration ONLY' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to change { SequelRails::Migrations.current_migration } + .from(nil) + .to('1273253849_add_twitter_handle_to_users.rb') + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + end + end + + describe 'db:migrate:down' do + let(:version) { nil } + around do |example| + env_value_before = ENV['VERSION'] + ENV['VERSION'] = version + example.run + ENV['VERSION'] = env_value_before + end + let(:rake_task_call) do + proc { Rake::Task['db:migrate:down'].execute } + end + + context 'when no version supplied' do + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error('VERSION is required') + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'when version with no matching migration supplied' do + let(:version) { '1273253848' } + + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error("Migration with version #{version} not found") + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'when version with matching migration supplied' do + let(:version) { '1365762738' } + + context 'and migration already run' do + it 'reverts the matching migration ONLY' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to change { SequelRails::Migrations.previous_migration } + .from('1365762738_add_display_name_to_users.rb') + .to('1273253849_add_twitter_handle_to_users.rb') + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + + context 'and migration NOT already run' do + before do + Dir.chdir app_root do + SequelRails::Migrations.migrate_down!(0) + end + end + + it 'raises error' do + Dir.chdir app_root do + begin + expect do + rake_task_call.call + end.to raise_error("Migration with version #{version} is already migrated/rollback") + ensure + SequelRails::Migrations.migrate_up! + end + end + end + end + end + end + describe 'db:rollback' do let(:version) { nil } let(:rake_task_call) do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index df7a0cc4..f902b7c1 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,8 @@ # Combustion initialization has to happen before loading rspec/rails Combustion.initialize! :sequel_rails +# Load rake tasks +Combustion::Application.load_tasks require 'rspec/rails' require 'ammeter/init'