Skip to content

Commit

Permalink
Add pre migrations to allow more control of when data migrations can …
Browse files Browse the repository at this point in the history
…be run
  • Loading branch information
exosyphon-earthvectors committed Jul 20, 2019
1 parent 18884e2 commit a6b5856
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 20 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
##### Note: All migrations (including Pre Migrations) will still be run when running `rake data:migrate`
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
Expand Down
14 changes: 14 additions & 0 deletions lib/generators/pre_data_migration_generator.rb
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions lib/tasks/data_migrations.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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_migrations.select { |m| m.filename.match(/\d{14}_pre_/) }.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'])
Expand Down
80 changes: 60 additions & 20 deletions spec/data_migrations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 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
Expand All @@ -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
Expand All @@ -64,11 +93,22 @@ def load_rake_rasks

2.times do
Rake::Task['data:migrate'].execute
expect(RailsDataMigrations::Migrator.current_version).to eq(20161101020304)
expect(RailsDataMigrations::LogEntry.count).to eq(1)
expect(RailsDataMigrations::Migrator.current_version).to eq(20190101020314)
expect(RailsDataMigrations::LogEntry.count).to eq(2)
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

Expand All @@ -83,7 +123,7 @@ def load_rake_rasks

allow(RailsDataMigrations::Migrator).to receive(:run) { true }
Rake::Task['data:reset'].execute
expect(RailsDataMigrations::LogEntry.count).to eq(1)
expect(RailsDataMigrations::LogEntry.count).to eq(2)
expect(RailsDataMigrations::Migrator).not_to have_received(:run)
end
end
Expand Down
5 changes: 5 additions & 0 deletions spec/generators/20191101020314_pre_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class PreTest < ActiveRecord::DataMigration
def up
# put your code here
end
end

0 comments on commit a6b5856

Please sign in to comment.