online_migrations 0.36.0 → 0.37.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: 6740adaa6d20273e8d148e8e0788a6e27c6c6701187fe60e006ff3ba8e2129d2
4
- data.tar.gz: 445a3ef72888dd358cc5d917ca31de7499df3368797c9c73324704e0e2d8307a
3
+ metadata.gz: cdad1569af01bacbaa092898b51ba64ad8f6c9746c091441af38b11921bbe2cc
4
+ data.tar.gz: 7f3d23f9e0d5b78fb958322e852bcc274e73ef9c035e05e2e072bec1ebcca086
5
5
  SHA512:
6
- metadata.gz: 338cf3a26b64efb827b5691addc67987048e0522f467d89c5ff4a18494f6b0e3cbeb93513e419a01b04be643d9f7e7a36c1aaf1b0a9d7f5c8158ecff1eb315e7
7
- data.tar.gz: 4a6b9282a87124e037b3f101a5856d4a8756e86f9fb5b8bf3a4e42e7e2790e0edbd78b7263b8e9c653bedc25700d8a3fb8ade671e2f99e1e74dbfd49780c20eb
6
+ metadata.gz: 2fce7817c096a2ca24fed7f8f4984a28bb12a5b4992dd395da4b6ae843ed39684f0917ad0f112c81d7b46ee178a2f3424d7ac9426d32b8ed4a1a83251398985f
7
+ data.tar.gz: e2cd67a8215dbb287ebdd306c574cbbbfaaafcd5c55b0fb2ec2f393f363870ad3e228dd83fedca3b44287baf148504edf2ca191103e179b63e92675636328c4b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.37.0 (2026-09-23)
4
+
5
+ - Add ability to restart background migrations
6
+ - Fix collecting referenced tables when using `foreign_key: { to_table: ... }`
7
+
8
+ ## 0.36.1 (2026-09-23)
9
+
10
+ - Fix compatibility with future Rails 8.2
11
+ - Fix pausing already paused background data migrations
12
+
3
13
  ## 0.36.0 (2026-07-20)
4
14
 
5
15
  - Serialize to JSON `cursor` value for background data migrations
@@ -354,6 +354,18 @@ migration.retry # => `true` if scheduled to be retried, `false` - if not
354
354
 
355
355
  The migration will be retried on the next Scheduler run.
356
356
 
357
+ ## Restarting a migration
358
+
359
+ To restart a migration, run:
360
+
361
+ ```ruby
362
+ migration = OnlineMigrations::DataMigrations::Migration.find(id)
363
+ migration.force_restart
364
+ ```
365
+
366
+ It ignores any previous status and marks the migration for the restart. Can be useful if the migration is stuck or
367
+ you need to rerun an already finished migration etc.
368
+
357
369
  ## Configuring
358
370
 
359
371
  There are a few configurable options for the data migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
@@ -77,7 +77,7 @@ You shouldn't depend on the schema until the background schema migration is fini
77
77
  To retry a failed migration, run:
78
78
 
79
79
  ```ruby
80
- migration = OnlineMigrations::BackgroundSchemaMigrations::Migration.find(id)
80
+ migration = OnlineMigrations::SchemaMigrations::Migration.find(id)
81
81
  migration.retry # => `true` if scheduled to be retried, `false` - if not
82
82
  ```
83
83
 
@@ -88,10 +88,22 @@ The migration will be retried on the next Scheduler run.
88
88
  To cancel an existing migration from future performing, run:
89
89
 
90
90
  ```ruby
91
- migration = OnlineMigrations::BackgroundSchemaMigrations::Migration.find(id)
91
+ migration = OnlineMigrations::SchemaMigrations::Migration.find(id)
92
92
  migration.cancel
93
93
  ```
94
94
 
95
+ ## Restarting a migration
96
+
97
+ To restart a migration, run:
98
+
99
+ ```ruby
100
+ migration = OnlineMigrations::SchemaMigrations::Migration.find(id)
101
+ migration.force_restart
102
+ ```
103
+
104
+ It ignores any previous status and marks the migration for the restart. Can be useful if the migration is stuck or
105
+ you need to rerun a cancelled migration.
106
+
95
107
  ## Instrumentation
96
108
 
97
109
  Background schema migrations use the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
@@ -156,7 +168,7 @@ Check the [source code](https://github.com/fatkodima/online_migrations/blob/mast
156
168
  **Note**: You can dynamically change certain migration parameters while the migration is run.
157
169
  For example,
158
170
  ```ruby
159
- migration = OnlineMigrations::BackgroundSchemaMigrations::Migration.find(id)
171
+ migration = OnlineMigrations::SchemaMigrations::Migration.find(id)
160
172
  migration.update!(
161
173
  statement_timeout: 2.hours, # The statement timeout value used when running the migration
162
174
  max_attempts: 10 # The # of attempts the failing migration will be retried
@@ -182,7 +194,7 @@ end
182
194
  The error handler should be a lambda that accepts 2 arguments:
183
195
 
184
196
  * `error`: The exception that was raised.
185
- * `errored_migration`: An `OnlineMigrations::BackgroundSchemaMigrations::Migration` object that represents a failed migration.
197
+ * `errored_migration`: An `OnlineMigrations::SchemaMigrations::Migration` object that represents a failed migration.
186
198
 
187
199
  ### Multiple databases and sharding
188
200
 
@@ -177,12 +177,12 @@ module OnlineMigrations
177
177
  true
178
178
  end
179
179
 
180
- # Pause this data migration. No-op if migration is completed.
180
+ # Pause this data migration. No-op if migration is completed or already paused.
181
181
  #
182
182
  # @return [Boolean] whether this data migration was paused.
183
183
  #
184
184
  def pause
185
- return false if completed?
185
+ return false if paused? || completed?
186
186
 
187
187
  if pending? || enqueued? || delayed? || stuck? || errored?
188
188
  paused!
@@ -240,6 +240,24 @@ module OnlineMigrations
240
240
  true
241
241
  end
242
242
 
243
+ # Force restart this data migration.
244
+ #
245
+ def force_restart
246
+ update_columns(
247
+ status: :pending,
248
+ cursor: nil,
249
+ jid: nil,
250
+ started_at: nil,
251
+ finished_at: nil,
252
+ tick_total: nil,
253
+ tick_count: 0,
254
+ time_running: 0.0,
255
+ error_class: nil,
256
+ error_message: nil,
257
+ backtrace: nil
258
+ )
259
+ end
260
+
243
261
  # @private
244
262
  def persist_progress(cursor, number_of_ticks, duration)
245
263
  update!(
@@ -135,6 +135,20 @@ module OnlineMigrations
135
135
  end
136
136
  end
137
137
 
138
+ # Force restart this schema migration.
139
+ #
140
+ def force_restart
141
+ update_columns(
142
+ status: :pending,
143
+ started_at: nil,
144
+ finished_at: nil,
145
+ attempts: 0,
146
+ error_class: nil,
147
+ error_message: nil,
148
+ backtrace: nil
149
+ )
150
+ end
151
+
138
152
  def index_addition?
139
153
  definition.match?(/create (unique )?index/i)
140
154
  end
@@ -490,9 +490,10 @@ module OnlineMigrations
490
490
  bad_index = index && !concurrently_set
491
491
 
492
492
  foreign_key = options.fetch(:foreign_key, false)
493
+ foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
493
494
 
494
495
  if foreign_key
495
- foreign_table_name = Utils.foreign_table_name(ref_name, options)
496
+ foreign_table_name = Utils.foreign_table_name(ref_name, foreign_key_options)
496
497
  @foreign_key_tables << foreign_table_name.to_s
497
498
  end
498
499
 
@@ -519,9 +520,10 @@ module OnlineMigrations
519
520
 
520
521
  def add_reference_concurrently(table_name, ref_name, **options)
521
522
  foreign_key = options.fetch(:foreign_key, false)
523
+ foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
522
524
 
523
525
  if foreign_key
524
- foreign_table_name = Utils.foreign_table_name(ref_name, options)
526
+ foreign_table_name = Utils.foreign_table_name(ref_name, foreign_key_options)
525
527
  @foreign_key_tables << foreign_table_name.to_s
526
528
  end
527
529
 
@@ -529,7 +531,6 @@ module OnlineMigrations
529
531
  type = (options[:type] || :bigint).to_sym
530
532
  column_name = "#{ref_name}_id"
531
533
 
532
- foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
533
534
  check_mismatched_foreign_key_type(table_name, column_name, type, **foreign_key_options)
534
535
  end
535
536
  end
@@ -153,4 +153,157 @@ module OnlineMigrations
153
153
  [:add_text_limit_constraint, args]
154
154
  end
155
155
  end
156
+
157
+ # @private
158
+ # New format was introduced in https://github.com/rails/rails/pull/58239.
159
+ module CommandRecorder82
160
+ REVERSIBLE_AND_IRREVERSIBLE_METHODS = [
161
+ :update_column_in_batches,
162
+ :initialize_column_rename,
163
+ :initialize_columns_rename,
164
+ :revert_initialize_column_rename,
165
+ :revert_initialize_columns_rename,
166
+ :finalize_column_rename,
167
+ :finalize_columns_rename,
168
+ :revert_finalize_column_rename,
169
+ :revert_finalize_columns_rename,
170
+ :initialize_table_rename,
171
+ :revert_initialize_table_rename,
172
+ :finalize_table_rename,
173
+ :revert_finalize_table_rename,
174
+ :swap_column_names,
175
+ :add_column_with_default,
176
+ :add_not_null_constraint,
177
+ :remove_not_null_constraint,
178
+ :add_text_limit_constraint,
179
+ :remove_text_limit_constraint,
180
+ :add_reference_concurrently,
181
+ :change_column_type_in_background,
182
+ :enqueue_background_data_migration,
183
+ :remove_background_data_migration,
184
+
185
+ # column type change helpers
186
+ :initialize_column_type_change,
187
+ :initialize_columns_type_change,
188
+ :revert_initialize_column_type_change,
189
+ :revert_initialize_columns_type_change,
190
+ :backfill_column_for_type_change,
191
+ :backfill_columns_for_type_change,
192
+ :finalize_column_type_change,
193
+ :finalize_columns_type_change,
194
+ :revert_finalize_column_type_change,
195
+ :cleanup_column_type_change,
196
+ :cleanup_columns_type_change,
197
+ ]
198
+
199
+ REVERSIBLE_AND_IRREVERSIBLE_METHODS.each do |method|
200
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
201
+ def #{method}(*args, **kwargs, &block) # def create_table(*args, **kwargs, &block)
202
+ record(:"#{method}", args, kwargs, &block) # record(:create_table, args, kwargs, &block)
203
+ end # end
204
+ RUBY
205
+ end
206
+
207
+ private
208
+ module StraightReversions
209
+ {
210
+ initialize_column_rename: :revert_initialize_column_rename,
211
+ initialize_columns_rename: :revert_initialize_columns_rename,
212
+ finalize_column_rename: :revert_finalize_column_rename,
213
+ finalize_columns_rename: :revert_finalize_columns_rename,
214
+ initialize_table_rename: :revert_initialize_table_rename,
215
+ finalize_table_rename: :revert_finalize_table_rename,
216
+ add_not_null_constraint: :remove_not_null_constraint,
217
+ initialize_column_type_change: :revert_initialize_column_type_change,
218
+ initialize_columns_type_change: :revert_initialize_columns_type_change,
219
+ finalize_column_type_change: :revert_finalize_column_type_change,
220
+ finalize_columns_type_change: :revert_finalize_columns_type_change,
221
+ }.each do |cmd, inv|
222
+ [[inv, cmd], [cmd, inv]].each do |method, inverse|
223
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
224
+ def invert_#{method}(args, kwargs, &block) # def invert_create_table(args, kwargs, &block)
225
+ [:#{inverse}, args, kwargs, block] # [:drop_table, args, kwargs, block]
226
+ end # end
227
+ RUBY
228
+ end
229
+ end
230
+ end
231
+
232
+ include StraightReversions
233
+
234
+ def invert_add_reference_concurrently(args, kwargs)
235
+ [:remove_reference, args, kwargs]
236
+ end
237
+
238
+ def invert_swap_column_names(args, kwargs)
239
+ table_name, column1, column2 = args
240
+ [:swap_column_names, [table_name, column2, column1], kwargs]
241
+ end
242
+
243
+ def invert_add_column_with_default(args, kwargs)
244
+ table_name, column_name, = args
245
+ [:remove_column, [table_name, column_name], kwargs]
246
+ end
247
+
248
+ def invert_revert_initialize_column_rename(args, kwargs)
249
+ _table, column, new_column = args
250
+ if !column || !new_column
251
+ raise ActiveRecord::IrreversibleMigration,
252
+ "revert_initialize_column_rename is only reversible if given a column and new_column."
253
+ end
254
+ [:initialize_column_rename, args, kwargs]
255
+ end
256
+
257
+ def invert_revert_initialize_columns_rename(args, kwargs)
258
+ _table, old_new_column_hash = args
259
+ if !old_new_column_hash
260
+ raise ActiveRecord::IrreversibleMigration,
261
+ "revert_initialize_columns_rename is only reversible if given a hash of old and new columns."
262
+ end
263
+ [:initialize_columns_rename, args, kwargs]
264
+ end
265
+
266
+ def invert_finalize_table_rename(args, kwargs)
267
+ _table_name, new_name = args
268
+ if !new_name
269
+ raise ActiveRecord::IrreversibleMigration,
270
+ "finalize_table_rename is only reversible if given a new_name."
271
+ end
272
+ [:revert_finalize_table_rename, args, kwargs]
273
+ end
274
+
275
+ def invert_revert_initialize_column_type_change(args, kwargs)
276
+ if !args[2]
277
+ raise ActiveRecord::IrreversibleMigration,
278
+ "revert_initialize_column_type_change is only reversible if given a new_type."
279
+ end
280
+ super
281
+ end
282
+
283
+ def invert_revert_initialize_columns_type_change(args, kwargs)
284
+ if args[1].empty?
285
+ raise ActiveRecord::IrreversibleMigration,
286
+ "revert_initialize_columns_type_change is only reversible if given a columns_and_types."
287
+ end
288
+ super
289
+ end
290
+
291
+ def invert_add_not_null_constraint(args, kwargs)
292
+ kwargs.delete(:validate)
293
+ [:remove_not_null_constraint, args, kwargs]
294
+ end
295
+
296
+ def invert_add_text_limit_constraint(args, kwargs)
297
+ kwargs.delete(:validate)
298
+ [:remove_text_limit_constraint, args, kwargs]
299
+ end
300
+
301
+ def invert_remove_text_limit_constraint(args, kwargs)
302
+ if !args[2]
303
+ raise ActiveRecord::IrreversibleMigration, "remove_text_limit_constraint is only reversible if given a limit."
304
+ end
305
+
306
+ [:add_text_limit_constraint, args, kwargs]
307
+ end
308
+ end
156
309
  end
@@ -18,9 +18,12 @@ module OnlineMigrations
18
18
  end
19
19
 
20
20
  def references(*ref_names, **options)
21
- if options[:foreign_key]
21
+ foreign_key = options[:foreign_key]
22
+
23
+ if foreign_key
24
+ foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
22
25
  ref_names.each do |ref_name|
23
- @referenced_tables << Utils.foreign_table_name(ref_name, options)
26
+ @referenced_tables << Utils.foreign_table_name(ref_name, foreign_key_options)
24
27
  end
25
28
  end
26
29
  end
@@ -79,6 +79,14 @@ module OnlineMigrations
79
79
  new_column = old_column.dup
80
80
  # Active Record defines only reader for :name
81
81
  new_column.instance_variable_set(:@name, new_column_name)
82
+
83
+ # https://github.com/rails/rails/commit/0c5b92705960ceff44324e69a921e760f1646503
84
+ # New in Active Record 8.2+.
85
+ if new_column.respond_to?(:slot)
86
+ max_slot = columns.map(&:slot).max
87
+ new_column.instance_variable_set(:@slot, max_slot + 1)
88
+ end
89
+
82
90
  # Correspond to the Active Record freezing of each column
83
91
  columns << new_column.freeze
84
92
  end
@@ -15,7 +15,13 @@ module OnlineMigrations
15
15
 
16
16
  class WrappedConnection < SimpleDelegator
17
17
  def columns(table_name)
18
- super.sort_by(&:name)
18
+ columns = super
19
+
20
+ if Utils.ar_version >= 8.2 && columns.is_a?(Hash)
21
+ columns.transform_values { |v| v.sort_by(&:name) }
22
+ else
23
+ columns.sort_by(&:name)
24
+ end
19
25
  end
20
26
  end
21
27
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.36.0"
4
+ VERSION = "0.37.0"
5
5
  end
@@ -119,7 +119,12 @@ module OnlineMigrations
119
119
  ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
120
120
 
121
121
  ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(OnlineMigrations::DatabaseTasks)
122
- ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder)
122
+
123
+ if Utils.ar_version >= 8.2
124
+ ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder82)
125
+ else
126
+ ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder)
127
+ end
123
128
 
124
129
  if !ActiveRecord::Batches::BatchEnumerator.method_defined?(:use_ranges)
125
130
  ActiveRecord::Batches::BatchEnumerator.include(OnlineMigrations::ActiveRecordBatchEnumerator)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: online_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.36.0
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 4.0.14
119
+ rubygems_version: 4.0.20
120
120
  specification_version: 4
121
121
  summary: Catch unsafe PostgreSQL migrations in development and run them easier in
122
122
  production