diff --git a/README.md b/README.md index dcdc9882..e6c2972e 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,31 @@ rake data:migrate This will run all pending data migrations and store migration history in `data_migrations` table. You're all set. +If you need to generate migrations that can be run separately from data_migrations you can create Pre Migrations + +To create a pre data migration you need to run: +``` +rails generate pre_data_migration migration_name +``` + +and this will create a `pre_migration_name.rb` file in `db/data_migrations` folder with a following content: +```ruby +class PreMigrationName < DataMigration + def up + # put your code here + end +end +``` + +so all we need to do is to put some ruby code inside the `up` method. + +Finally, at the release time, you need to run +``` +rake data:migrate:pre +``` + +This will run all pending pre data migrations and store migration history in `data_migrations` table. You're all set. + ## Rails Support Rails 4.0 and higher diff --git a/lib/generators/pre_data_migration_generator.rb b/lib/generators/pre_data_migration_generator.rb new file mode 100644 index 00000000..b50b726b --- /dev/null +++ b/lib/generators/pre_data_migration_generator.rb @@ -0,0 +1,14 @@ +require 'rails/generators' +require 'rails-data-migrations' + +class PreDataMigrationGenerator < Rails::Generators::NamedBase + source_root File.expand_path('../templates', __FILE__) + + def create_migration_file + migration_file_name = + "#{RailsDataMigrations::Migrator.migrations_path}/#{Time.now.utc.strftime('%Y%m%d%H%M%S')}_pre_#{file_name}.rb" + copy_file 'data_migration_generator.rb', migration_file_name do |content| + content.sub(/ClassName/, "Pre#{file_name.camelize}") + end + end +end diff --git a/lib/rails_data_migrations/migrator.rb b/lib/rails_data_migrations/migrator.rb index 567ff8b4..443d964d 100644 --- a/lib/rails_data_migrations/migrator.rb +++ b/lib/rails_data_migrations/migrator.rb @@ -53,9 +53,22 @@ def list_migrations def list_pending_migrations if rails_5_2? already_migrated = get_all_versions - list_migrations.reject { |m| already_migrated.include?(m.version) } + list_migrations + .reject { |m| already_migrated.include?(m.version) } + .reject { |m| m.filename.match(/\d{14}_pre_/) } else - open(migrations_path).pending_migrations + open(migrations_path).pending_migrations.reject { |m| m.filename.match(/\d{14}_pre_/) } + end + end + + def list_pending_pre_migrations + if rails_5_2? + already_migrated = get_all_versions + list_migrations + .reject { |m| already_migrated.include?(m.version) } + .select { |m| m.filename.match(/\d{14}_pre_/) } + else + open(migrations_path).pending_migrations.select { |m| m.filename.match(/\d{14}_pre_/) } end end diff --git a/lib/tasks/data_migrations.rake b/lib/tasks/data_migrations.rake index 65a654e2..fc2ec4fe 100644 --- a/lib/tasks/data_migrations.rake +++ b/lib/tasks/data_migrations.rake @@ -34,6 +34,13 @@ namespace :data do apply_single_migration(:up, ENV['VERSION']) end + desc 'Apply pre data migrations' + task pre: :init_migration do + RailsDataMigrations::Migrator.list_pending_pre_migrations.sort_by(&:version).each do |m| + apply_single_migration(:up, m.version) + end + end + desc 'Revert single data migration using VERSION' task down: :init_migration do apply_single_migration(:down, ENV['VERSION']) diff --git a/spec/data_migrations_spec.rb b/spec/data_migrations_spec.rb index 5f02138f..923875d2 100644 --- a/spec/data_migrations_spec.rb +++ b/spec/data_migrations_spec.rb @@ -14,33 +14,62 @@ context 'generator' do let(:migration_name) { 'test_migration' } - let(:file_name) { 'spec/db/data-migrations/20161031000000_test_migration.rb' } + context 'regular migration' do + let(:file_name) { 'spec/db/data-migrations/20161031000000_test_migration.rb' } - before(:each) do - allow(Time).to receive(:now).and_return(Time.utc(2016, 10, 31)) - Rails::Generators.invoke('data_migration', [migration_name]) - end + before(:each) do + allow(Time).to receive(:now).and_return(Time.utc(2016, 10, 31)) + Rails::Generators.invoke('data_migration', [migration_name]) + end + + it 'creates non-empty migration file' do + expect(File.exist?(file_name)).to be_truthy + expect(File.size(file_name)).to be > 0 + end - it 'creates non-empty migration file' do - expect(File.exist?(file_name)).to be_truthy - expect(File.size(file_name)).to be > 0 + it 'creates valid migration class' do + # rubocop:disable Security/Eval + eval(File.open(file_name).read) + # rubocop:enable Security/Eval + klass = migration_name.classify.constantize + expect(klass.superclass).to eq(ActiveRecord::DataMigration) + expect(klass.instance_methods(false)).to eq([:up]) + end end - it 'creates valid migration class' do - # rubocop:disable Security/Eval - eval(File.open(file_name).read) - # rubocop:enable Security/Eval - klass = migration_name.classify.constantize - expect(klass.superclass).to eq(ActiveRecord::DataMigration) - expect(klass.instance_methods(false)).to eq([:up]) + context 'pre migration' do + let(:file_name) { 'spec/db/data-migrations/20190131000000_pre_test_migration.rb' } + + before(:each) do + allow(Time).to receive(:now).and_return(Time.utc(2019, 1, 31)) + Rails::Generators.invoke('pre_data_migration', [migration_name]) + end + + it 'creates non-empty pre migration file' do + expect(File.exist?(file_name)).to be_truthy + expect(File.size(file_name)).to be > 0 + end + + it 'creates valid pre migration class' do + # rubocop:disable Security/Eval + eval(File.open(file_name).read) + # rubocop:enable Security/Eval + klass = "pre_#{migration_name}".classify.constantize + expect(klass.superclass).to eq(ActiveRecord::DataMigration) + expect(klass.instance_methods(false)).to eq([:up]) + end end end context 'migrator' do before(:each) do - allow(Time).to receive(:now).and_return(Time.utc(2016, 11, 1, 2, 3, 4)) + allow(Time).to receive(:now).and_return( + Time.utc(2016, 11, 1, 2, 3, 4), + Time.utc(2019, 1, 1, 2, 3, 14) + ) Rails::Generators.invoke('data_migration', ['test']) + Rails::Generators.invoke('pre_data_migration', ['test']) end def load_rake_rasks @@ -54,7 +83,7 @@ def load_rake_rasks end it 'list migration file' do - expect(RailsDataMigrations::Migrator.list_migrations.size).to eq(1) + expect(RailsDataMigrations::Migrator.list_migrations.size).to eq(2) end it 'applies pending migrations only once' do @@ -69,6 +98,17 @@ def load_rake_rasks end end + it 'applies pending pre migrations' do + expect(RailsDataMigrations::LogEntry.count).to eq(0) + + load_rake_rasks + + Rake::Task['data:migrate:pre'].execute + + expect(RailsDataMigrations::Migrator.current_version).to eq(20190101020314) + expect(RailsDataMigrations::LogEntry.count).to eq(1) + end + it 'requires VERSION to run a single migration' do ENV['VERSION'] = nil