Skip to content

Commit b210e14

Browse files
Add pre migrations to allow more control of when data migrations can be run
1 parent 18884e2 commit b210e14

File tree

5 files changed

+118
-19
lines changed

5 files changed

+118
-19
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,31 @@ rake data:migrate
3636

3737
This will run all pending data migrations and store migration history in `data_migrations` table. You're all set.
3838

39+
If you need to generate migrations that can be run separately from data_migrations you can create Pre Migrations
40+
41+
To create a pre data migration you need to run:
42+
```
43+
rails generate pre_data_migration migration_name
44+
```
45+
46+
and this will create a `pre_migration_name.rb` file in `db/data_migrations` folder with a following content:
47+
```ruby
48+
class PreMigrationName < DataMigration
49+
def up
50+
# put your code here
51+
end
52+
end
53+
```
54+
55+
so all we need to do is to put some ruby code inside the `up` method.
56+
57+
Finally, at the release time, you need to run
58+
```
59+
rake data:migrate:pre
60+
```
61+
62+
This will run all pending pre data migrations and store migration history in `data_migrations` table. You're all set.
63+
3964
## Rails Support
4065

4166
Rails 4.0 and higher
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require 'rails/generators'
2+
require 'rails-data-migrations'
3+
4+
class PreDataMigrationGenerator < Rails::Generators::NamedBase
5+
source_root File.expand_path('../templates', __FILE__)
6+
7+
def create_migration_file
8+
migration_file_name =
9+
"#{RailsDataMigrations::Migrator.migrations_path}/#{Time.now.utc.strftime('%Y%m%d%H%M%S')}_pre_#{file_name}.rb"
10+
copy_file 'data_migration_generator.rb', migration_file_name do |content|
11+
content.sub(/ClassName/, "Pre#{file_name.camelize}")
12+
end
13+
end
14+
end

lib/rails_data_migrations/migrator.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,22 @@ def list_migrations
5353
def list_pending_migrations
5454
if rails_5_2?
5555
already_migrated = get_all_versions
56-
list_migrations.reject { |m| already_migrated.include?(m.version) }
56+
list_migrations
57+
.reject { |m| already_migrated.include?(m.version) }
58+
.reject { |m| m.filename.match(/\d{14}_pre_/) }
5759
else
58-
open(migrations_path).pending_migrations
60+
open(migrations_path).pending_migrations.reject { |m| m.filename.match(/\d{14}_pre_/) }
61+
end
62+
end
63+
64+
def list_pending_pre_migrations
65+
if rails_5_2?
66+
already_migrated = get_all_versions
67+
list_migrations
68+
.reject { |m| already_migrated.include?(m.version) }
69+
.select { |m| m.filename.match(/\d{14}_pre_/) }
70+
else
71+
open(migrations_path).pending_migrations.select { |m| m.filename.match(/\d{14}_pre_/) }
5972
end
6073
end
6174

lib/tasks/data_migrations.rake

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ namespace :data do
3434
apply_single_migration(:up, ENV['VERSION'])
3535
end
3636

37+
desc 'Apply pre data migrations'
38+
task pre: :init_migration do
39+
RailsDataMigrations::Migrator.list_pending_pre_migrations.sort_by(&:version).each do |m|
40+
apply_single_migration(:up, m.version)
41+
end
42+
end
43+
3744
desc 'Revert single data migration using VERSION'
3845
task down: :init_migration do
3946
apply_single_migration(:down, ENV['VERSION'])

spec/data_migrations_spec.rb

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,62 @@
1414
context 'generator' do
1515
let(:migration_name) { 'test_migration' }
1616

17-
let(:file_name) { 'spec/db/data-migrations/20161031000000_test_migration.rb' }
17+
context 'regular migration' do
18+
let(:file_name) { 'spec/db/data-migrations/20161031000000_test_migration.rb' }
1819

19-
before(:each) do
20-
allow(Time).to receive(:now).and_return(Time.utc(2016, 10, 31))
21-
Rails::Generators.invoke('data_migration', [migration_name])
22-
end
20+
before(:each) do
21+
allow(Time).to receive(:now).and_return(Time.utc(2016, 10, 31))
22+
Rails::Generators.invoke('data_migration', [migration_name])
23+
end
24+
25+
it 'creates non-empty migration file' do
26+
expect(File.exist?(file_name)).to be_truthy
27+
expect(File.size(file_name)).to be > 0
28+
end
2329

24-
it 'creates non-empty migration file' do
25-
expect(File.exist?(file_name)).to be_truthy
26-
expect(File.size(file_name)).to be > 0
30+
it 'creates valid migration class' do
31+
# rubocop:disable Security/Eval
32+
eval(File.open(file_name).read)
33+
# rubocop:enable Security/Eval
34+
klass = migration_name.classify.constantize
35+
expect(klass.superclass).to eq(ActiveRecord::DataMigration)
36+
expect(klass.instance_methods(false)).to eq([:up])
37+
end
2738
end
2839

29-
it 'creates valid migration class' do
30-
# rubocop:disable Security/Eval
31-
eval(File.open(file_name).read)
32-
# rubocop:enable Security/Eval
33-
klass = migration_name.classify.constantize
34-
expect(klass.superclass).to eq(ActiveRecord::DataMigration)
35-
expect(klass.instance_methods(false)).to eq([:up])
40+
context 'pre migration' do
41+
let(:file_name) { 'spec/db/data-migrations/20190131000000_pre_test_migration.rb' }
42+
43+
before(:each) do
44+
allow(Time).to receive(:now).and_return(Time.utc(2019, 1, 31))
45+
Rails::Generators.invoke('pre_data_migration', [migration_name])
46+
end
47+
48+
it 'creates non-empty pre migration file' do
49+
expect(File.exist?(file_name)).to be_truthy
50+
expect(File.size(file_name)).to be > 0
51+
end
52+
53+
it 'creates valid pre migration class' do
54+
# rubocop:disable Security/Eval
55+
eval(File.open(file_name).read)
56+
# rubocop:enable Security/Eval
57+
klass = "pre_#{migration_name}".classify.constantize
58+
expect(klass.superclass).to eq(ActiveRecord::DataMigration)
59+
expect(klass.instance_methods(false)).to eq([:up])
60+
end
3661
end
3762
end
3863

3964
context 'migrator' do
4065
before(:each) do
41-
allow(Time).to receive(:now).and_return(Time.utc(2016, 11, 1, 2, 3, 4))
66+
allow(Time).to receive(:now).and_return(
67+
Time.utc(2016, 11, 1, 2, 3, 4),
68+
Time.utc(2019, 1, 1, 2, 3, 14)
69+
)
4270

4371
Rails::Generators.invoke('data_migration', ['test'])
72+
Rails::Generators.invoke('pre_data_migration', ['test'])
4473
end
4574

4675
def load_rake_rasks
@@ -54,7 +83,7 @@ def load_rake_rasks
5483
end
5584

5685
it 'list migration file' do
57-
expect(RailsDataMigrations::Migrator.list_migrations.size).to eq(1)
86+
expect(RailsDataMigrations::Migrator.list_migrations.size).to eq(2)
5887
end
5988

6089
it 'applies pending migrations only once' do
@@ -69,6 +98,17 @@ def load_rake_rasks
6998
end
7099
end
71100

101+
it 'applies pending pre migrations' do
102+
expect(RailsDataMigrations::LogEntry.count).to eq(0)
103+
104+
load_rake_rasks
105+
106+
Rake::Task['data:migrate:pre'].execute
107+
108+
expect(RailsDataMigrations::Migrator.current_version).to eq(20190101020314)
109+
expect(RailsDataMigrations::LogEntry.count).to eq(1)
110+
end
111+
72112
it 'requires VERSION to run a single migration' do
73113
ENV['VERSION'] = nil
74114

0 commit comments

Comments
 (0)