data-migration 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f317e1c1d38d56179e7236be0d37be8105859b653792c8eb4bdae60c0010affa
4
- data.tar.gz: df14fb4e41b46c7fffebf89d9367b810128216f285b15da18d8c5b3297f653d4
3
+ metadata.gz: d0a50f98ed1ef1b0b960cc713e71d64d41219933e0f7071fa9462c8adf69bb18
4
+ data.tar.gz: d169ccd0f77204e3ce81a10d5c9d39f4b320572a8ca2e269c8502bfea6b8ecc1
5
5
  SHA512:
6
- metadata.gz: 11da0da9e0b6593df4bd94a728d66f2ecbd5672e3a9819ec90bf58d32f811053b7e8f7890631faa7ea99253f9652e14f5383ece6af4f367ba16971a8cd7b1051
7
- data.tar.gz: e93699e6817f780e7d12457742bd4d053ad029b6478f1a86cce911a75bfb49b6567bfb8822030dfdd8a1474fc4ffc47d6ec92519d041449250643b6b7c2c784f
6
+ metadata.gz: b11fdc6a2879a38416e43b35bc2ea427c4396ca60977cfd919cd945f5563baaf07e398ce1d0cca42f71a936b01a6095ae971bf7320f8eced0059fa19a8c88eb8
7
+ data.tar.gz: d6d738963fe7473eb84984730f514cce3cebbf0a5ae2e4301734bca5e96c0cb8a5fdbe3998561136a27e1a0930c2162f2dbc713b19f9bded90b9f5dcdf463ac7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.1.0 (2026-11-09)
4
+
5
+ - Ship `bin/rails db:migrate:data` with `VERSION=` / `NAME=` selection and a migration list when both are blank.
6
+ - Keep the data migration operator on the task when enqueueing and in monitoring context.
7
+ - Report failed data migration job exceptions to ActiveJob hosts instead of discarding them.
8
+ - Reach full line coverage of packaged `lib/` and bind a local pressure probe with ceilings.
9
+
10
+ ## 2.0.1 (2026-10-28)
11
+
12
+ - Confirm gem release from a clean tree before tagging.
13
+
3
14
  ## 2.0.0 (2026-09-04)
4
15
 
5
16
  - BREAKING: Require Ruby 3.4 or newer.
data/README.md CHANGED
@@ -64,9 +64,11 @@ bin/rails g data_migration create_users
64
64
  ### Run data migrations
65
65
 
66
66
  ```sh
67
- bin/rails db:migrate:data 20241207120000_create_users
67
+ bin/rails db:migrate:data VERSION=20241207120000
68
68
  ```
69
69
 
70
+ `VERSION` may be the timestamp or the full `timestamp_name` file stem. `NAME=create_users` also works. With neither set, the task lists available migrations and their latest task status.
71
+
70
72
  ## Configuration
71
73
 
72
74
  ### Set data migrations directory
@@ -104,7 +106,7 @@ end
104
106
  - [x] User can generate data migration file under `db/data_migrations` directory with common format
105
107
  - [x] User can generate data migration file with test script included
106
108
  - [x] User can run specific data migration using Rails console
107
- - [ ] User can run specific data migration using shell command
109
+ - [x] User can run specific data migration using shell command
108
110
  - [x] User can run data migration in background
109
111
  - [x] User can run data migration in foreground
110
112
  - [x] User can specify operator for data migration
@@ -37,12 +37,9 @@ Gem::Specification.new do |gem|
37
37
 
38
38
  gem.add_development_dependency "bundler", ">= 2"
39
39
  gem.add_development_dependency "rspec", "~> 3"
40
- gem.add_development_dependency "polyrun", ">= 2.2.0"
40
+ gem.add_development_dependency "polyrun", ">= 2.2.9"
41
41
  gem.add_development_dependency "prosopite", "~> 2.0"
42
42
  gem.add_development_dependency "webmock", "~> 3"
43
- gem.add_development_dependency "rspec_junit_formatter", "~> 0.6"
44
- gem.add_development_dependency "simplecov", "~> 0.22"
45
- gem.add_development_dependency "simplecov-cobertura", "~> 3"
46
43
  gem.add_development_dependency "rbs", "~> 3"
47
44
  gem.add_development_dependency "standard", "~> 1.52"
48
45
  gem.add_development_dependency "standard-custom", "~> 1.0"
@@ -1,9 +1,12 @@
1
1
  require "data_migration/config"
2
2
  require "data_migration/job"
3
3
  require "data_migration/task"
4
+ require "data_migration/selector"
5
+ require "data_migration/cli"
6
+ require "data_migration/railtie" if defined?(Rails::Railtie)
4
7
 
5
8
  module DataMigration
6
- VERSION = "2.0.0".freeze
9
+ VERSION = "2.1.0".freeze
7
10
 
8
11
  module_function
9
12
 
@@ -12,7 +15,7 @@ module DataMigration
12
15
  end
13
16
 
14
17
  def configure
15
- yield config
18
+ yield(config)
16
19
  end
17
20
 
18
21
  def notify(message, context: {})
@@ -0,0 +1,20 @@
1
+ module DataMigration
2
+ module Cli
3
+ module_function
4
+
5
+ def run!(env = ENV)
6
+ version = env["VERSION"]
7
+ name = env["NAME"]
8
+
9
+ if version.to_s.strip.empty? && name.to_s.strip.empty?
10
+ puts "Available data migrations:"
11
+ DataMigration::Selector.status_lines.each { |line| puts line }
12
+ return :listed
13
+ end
14
+
15
+ migration_name = DataMigration::Selector.resolve(version: version, name: name)
16
+ DataMigration.perform_now(migration_name)
17
+ :ran
18
+ end
19
+ end
20
+ end
@@ -60,10 +60,10 @@ module DataMigration
60
60
  data_migration_operator_id: migration.operator&.try(:to_global_id) || migration.operator&.id
61
61
  }
62
62
  if Object.const_defined?(:ActionReporter)
63
- ActionReporter.current_user ||= migration.operator
63
+ ActionReporter.current_user = migration.operator if migration.operator
64
64
  ActionReporter.context(**context)
65
65
  elsif Object.const_defined?(:Audited)
66
- Audited.store[:audited_user] ||= migration.operator
66
+ Audited.store[:audited_user] = migration.operator if migration.operator
67
67
  else
68
68
  Rails.logger.info("Data migration context: #{context.inspect}")
69
69
  end
@@ -4,8 +4,6 @@ module DataMigration
4
4
  class Job < ActiveJob::Base
5
5
  queue_as { DataMigration.config.job_queue_name }
6
6
 
7
- discard_on StandardError
8
-
9
7
  def perform(task_id, *job_args, **job_kwargs)
10
8
  checked_in = false
11
9
  failed = false
@@ -45,13 +43,14 @@ module DataMigration
45
43
 
46
44
  Thread.current[:data_migration_enqueue_called] ||= {}
47
45
  Thread.current[:data_migration_enqueue_kwargs] ||= {}
48
- migration_class.define_method(:enqueue) do |**enqueue_kwargs|
46
+ migration = migration_class.new
47
+ migration.define_singleton_method(:enqueue) do |**enqueue_kwargs|
49
48
  Thread.current[:data_migration_enqueue_called][migration_class.name] = true
50
49
  Thread.current[:data_migration_enqueue_kwargs][migration_class.name] = enqueue_kwargs
51
50
  end
52
51
 
53
52
  task.update!(status: :performing, pause_minutes: 0)
54
- migration_class.new.perform(**job_kwargs)
53
+ migration.perform(**job_kwargs)
55
54
  task.job_check_out!(job_id)
56
55
  checked_in = false
57
56
 
@@ -0,0 +1,9 @@
1
+ require "rails/railtie"
2
+
3
+ module DataMigration
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load File.expand_path("tasks/data_migration.rake", __dir__)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ module DataMigration
2
+ class Selector
3
+ class Error < StandardError; end
4
+ class NotFound < Error; end
5
+ class Ambiguous < Error; end
6
+
7
+ def self.resolve(version: nil, name: nil)
8
+ new(version: version, name: name).resolve
9
+ end
10
+
11
+ def self.status_lines
12
+ DataMigration::Task.list.map do |migration_name|
13
+ task = DataMigration::Task.where(name: migration_name).order(id: :desc).first
14
+ status = task&.status || "not started"
15
+ "#{migration_name}\t#{status}"
16
+ end
17
+ end
18
+
19
+ def initialize(version: nil, name: nil)
20
+ @version = version.to_s.strip.presence
21
+ @name = name.to_s.strip.presence
22
+ end
23
+
24
+ def resolve
25
+ raise NotFound, "VERSION or NAME is required" if @version.nil? && @name.nil?
26
+
27
+ matches = matching_migrations
28
+ raise NotFound, "No data migration matches VERSION=#{@version.inspect} NAME=#{@name.inspect}" if matches.empty?
29
+ raise Ambiguous, "Multiple data migrations match: #{matches.join(", ")}" if matches.size > 1
30
+
31
+ matches.first
32
+ end
33
+
34
+ private
35
+
36
+ def matching_migrations
37
+ DataMigration::Task.list.select { |migration_name| match?(migration_name) }
38
+ end
39
+
40
+ def match?(migration_name)
41
+ return migration_name == @version || migration_name.start_with?("#{@version}_") if @version && @name.nil?
42
+ return migration_name == @name || migration_name.end_with?("_#{@name}") if @name && @version.nil?
43
+
44
+ combined = @version.include?("_") ? @version : "#{@version}_#{@name}"
45
+ migration_name == combined || migration_name == @version
46
+ end
47
+ end
48
+ end
@@ -54,25 +54,27 @@ module DataMigration
54
54
  end
55
55
 
56
56
  def self.perform_now(name, **kwargs)
57
- create!(name: name).perform_now(**kwargs)
57
+ create_with_operator!(name, **kwargs).perform_now(**kwargs)
58
58
  end
59
59
 
60
60
  def perform_now(**perform_args)
61
- update!(kwargs: perform_args)
62
- self.class.job_class.perform_now(id, **perform_args)
61
+ job_kwargs = assign_operator_from!(perform_args)
62
+ update!(kwargs: job_kwargs)
63
+ self.class.job_class.perform_now(id, **job_kwargs)
63
64
  end
64
65
 
65
66
  def self.perform_later(name, **kwargs)
66
- create!(name: name).perform_later(**kwargs)
67
+ create_with_operator!(name, **kwargs).perform_later(**kwargs)
67
68
  end
68
69
 
69
70
  def perform_later(**perform_args)
70
- update!(kwargs: perform_args)
71
- self.class.job_class.perform_later(id, **perform_args)
71
+ job_kwargs = assign_operator_from!(perform_args)
72
+ update!(kwargs: job_kwargs)
73
+ self.class.job_class.perform_later(id, **job_kwargs)
72
74
  end
73
75
 
74
- def self.prepare(name, pause_minutes: nil, jobs_limit: nil)
75
- create!(name: name, pause_minutes: pause_minutes, jobs_limit: jobs_limit)
76
+ def self.prepare(name, pause_minutes: nil, jobs_limit: nil, operator: nil)
77
+ create!(name: name, pause_minutes: pause_minutes, jobs_limit: jobs_limit, operator: operator)
76
78
  end
77
79
 
78
80
  def self.root_path
@@ -129,6 +131,23 @@ module DataMigration
129
131
 
130
132
  private
131
133
 
134
+ def self.create_with_operator!(name, **kwargs)
135
+ attributes = {name: name}
136
+ attributes[:operator] = kwargs[:operator] if kwargs.key?(:operator)
137
+ create!(**attributes)
138
+ end
139
+ private_class_method :create_with_operator!
140
+
141
+ def assign_operator_from!(perform_args)
142
+ job_kwargs = perform_args.dup
143
+ if job_kwargs.key?(:operator)
144
+ self.operator = job_kwargs.delete(:operator)
145
+ else
146
+ self.operator ||= DataMigration.config.operator_resolver.call
147
+ end
148
+ job_kwargs
149
+ end
150
+
132
151
  def file_should_exist
133
152
  errors.add(:name, "is not found") unless file_exists?
134
153
  end
@@ -0,0 +1,8 @@
1
+ namespace :db do
2
+ namespace :migrate do
3
+ desc "Run a data migration with VERSION= and/or NAME= (lists migrations when both are blank)"
4
+ task data: :environment do
5
+ DataMigration::Cli.run!
6
+ end
7
+ end
8
+ end
@@ -1,8 +1,8 @@
1
1
  # USAGE: within remote console run one of the following commands
2
- # - rails db:migrate:data <%= migration_number %>_<%= migration_file_name %>
2
+ # - bin/rails db:migrate:data VERSION=<%= migration_number %>
3
3
  # - DataMigration.perform_now("<%= migration_number %>_<%= migration_file_name %>")
4
4
  # - DataMigration.perform_later("<%= migration_number %>_<%= migration_file_name %>")
5
- # - DataMigration.prepare("<%= migration_number %>_<%= migration_file_name %>", pause: 1.minute, jobs_limit: 2).perform_later
5
+ # - DataMigration.prepare("<%= migration_number %>_<%= migration_file_name %>", pause_minutes: 1, jobs_limit: 2).perform_later
6
6
 
7
7
  class <%= migration_class_name %>
8
8
  def perform(**kwargs)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: data-migration
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -99,14 +99,14 @@ dependencies:
99
99
  requirements:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
- version: 2.2.0
102
+ version: 2.2.9
103
103
  type: :development
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
- version: 2.2.0
109
+ version: 2.2.9
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: prosopite
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -135,48 +135,6 @@ dependencies:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
137
  version: '3'
138
- - !ruby/object:Gem::Dependency
139
- name: rspec_junit_formatter
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: '0.6'
145
- type: :development
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: '0.6'
152
- - !ruby/object:Gem::Dependency
153
- name: simplecov
154
- requirement: !ruby/object:Gem::Requirement
155
- requirements:
156
- - - "~>"
157
- - !ruby/object:Gem::Version
158
- version: '0.22'
159
- type: :development
160
- prerelease: false
161
- version_requirements: !ruby/object:Gem::Requirement
162
- requirements:
163
- - - "~>"
164
- - !ruby/object:Gem::Version
165
- version: '0.22'
166
- - !ruby/object:Gem::Dependency
167
- name: simplecov-cobertura
168
- requirement: !ruby/object:Gem::Requirement
169
- requirements:
170
- - - "~>"
171
- - !ruby/object:Gem::Version
172
- version: '3'
173
- type: :development
174
- prerelease: false
175
- version_requirements: !ruby/object:Gem::Requirement
176
- requirements:
177
- - - "~>"
178
- - !ruby/object:Gem::Version
179
- version: '3'
180
138
  - !ruby/object:Gem::Dependency
181
139
  name: rbs
182
140
  requirement: !ruby/object:Gem::Requirement
@@ -330,9 +288,13 @@ files:
330
288
  - README.md
331
289
  - data-migration.gemspec
332
290
  - lib/data-migration.rb
291
+ - lib/data_migration/cli.rb
333
292
  - lib/data_migration/config.rb
334
293
  - lib/data_migration/job.rb
294
+ - lib/data_migration/railtie.rb
295
+ - lib/data_migration/selector.rb
335
296
  - lib/data_migration/task.rb
297
+ - lib/data_migration/tasks/data_migration.rake
336
298
  - lib/generators/data_migration/install_generator.rb
337
299
  - lib/generators/data_migration/templates/install_data_migration_tasks.rb.tt
338
300
  - lib/generators/data_migration_generator.rb