online_migrations 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4706d65d4ed40219b3680266294f9382d1db51ac168768b77f55e1a73a2d3de3
4
- data.tar.gz: b828d4c3d8cc9ef1268eeaa00f86bc3fda41546963334cb356916b1ad481b3bc
3
+ metadata.gz: 87bf9f8f917c2d14e8a2e5584a2710dec5ccd66051730b8bb1b5a0b765b70ccb
4
+ data.tar.gz: 0cbf86b34c7d62f1466c877df231c2d39f3f4d5c2bd4f4dbef080f56e3254a74
5
5
  SHA512:
6
- metadata.gz: f70ab747e025f34a546da3d97aad6d7a175006fca9c48a3ae402b1683715b1eeb5128213bec52612c9178e457a638f9eb7d370445cf43b7c164f6d9bb548026f
7
- data.tar.gz: e001963c1fc82ec925a4b1c917689171e35dc3c4f0fba61f8a29ec0e2f5b2fedefa6733828d81176b489a345113e054f5f174c007152fc407d55e3184853cc77
6
+ metadata.gz: 3184f370a4cb3a3fa150ff24644355bc6c928eaa88331a0e18ee6f2c2448917898f5165675381162701f49881018516533b97e2364204a5a6ebd8bb19b3063cc
7
+ data.tar.gz: 954f9d33eba0e94020e0c9de77fd47097cd192915ad60a6c08aff8ab6d7a54b06a6938a50191c6b75596ae06b427076809472098b90a633549f2208ca358c656
@@ -114,6 +114,12 @@ enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
114
114
  * **Isolation**: Background migrations should be isolated and not use application code (for example, models defined in `app/models`). Since these migrations can take a long time to run it's possible for new versions to be deployed while they are still running.
115
115
  * **Idempotence**: It should be safe to run `process_batch` multiple times for the same elements. It's important if the Background Migration errors and you run it again, because the same element that errored may be processed again. Make sure that in case that your migration job is going to be retried data integrity is guaranteed.
116
116
 
117
+ ## Predefined background migrations
118
+
119
+ * `BackfillColumn` - backfills column(s) with scalar values
120
+ * `CopyColumn` - copies data from one column(s) to other(s)
121
+ * `ResetCounters` - resets one or more counter caches to their correct value
122
+
117
123
  ## Testing
118
124
 
119
125
  At a minimum, it's recommended that the `#process_batch` method in your background migration is tested. You may also want to test the `#relation` and `#count` methods if they are sufficiently complex.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  ## master (unreleased)
2
2
 
3
+ - Lazy load this gem
4
+
5
+ - Add ability to reset counter caches using background migrations
6
+
7
+ ```ruby
8
+ class User < ApplicationRecord
9
+ has_many :projects
10
+ end
11
+
12
+ class Project < ApplicationRecord
13
+ belongs_to :user, counter_cache: true
14
+ end
15
+
16
+ class ResetUsersProjectsCount < ActiveRecord::Migration[7.0]
17
+ def up
18
+ reset_counters_in_background("User", :projects)
19
+ end
20
+ end
21
+ ```
22
+
23
+ - Accept `0` as `batch_pause` value for background migrations
24
+ - Ignore default scopes in `CopyColumn` and `BackfillColumn` background migrations
25
+ - Raise an error for unsupported database versions
26
+ - Fix backfilling code in suggestion for changing column's NOT NULL
27
+
28
+ New safe operations
29
+
30
+ - Changing between `text` and `citext` when not indexed
31
+ - Changing a `string` column to a `citext` column when not indexed
32
+ - Changing a `citext` column to a `string` column with no length limit
33
+ - Increasing the `:precision` of an `interval` column
34
+ - Changing a `cidr` column to an `inet` column
35
+ - Changing an `xml` column to a `text` column
36
+ - Changing an `xml` column to a `string` column with no `:limit`
37
+ - Changing a `bit` column to a `bit_varying` column
38
+ - Increasing or removing the `:limit` of a `bit_varying` column
39
+
40
+ New unsafe operations
41
+
42
+ - Decreasing `:precision` of a `datetime` column
43
+ - Decreasing `:limit` of a `timestamptz` column
44
+ - Decreasing `:limit` of a `bit_varying` column
45
+ - Adding a `:limit` to a `bit_varying` column
46
+
3
47
  ## 0.3.0 (2022-02-10)
4
48
 
5
49
  - Support ActiveRecord 7.0+ versioned schemas
data/README.md CHANGED
@@ -10,6 +10,10 @@ Catch unsafe PostgreSQL migrations in development and run them easier in product
10
10
 
11
11
  [![Build Status](https://github.com/fatkodima/online_migrations/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/fatkodima/online_migrations/actions/workflows/test.yml)
12
12
 
13
+ ## Cool, but there is a `strong_migrations` already
14
+
15
+ See [comparison to `strong_migrations`](#comparison-to-strong_migrations)
16
+
13
17
  ## Requirements
14
18
 
15
19
  - Ruby 2.1+
@@ -282,12 +286,20 @@ end
282
286
 
283
287
  A few changes don't require a table rewrite (and are safe) in PostgreSQL:
284
288
 
285
- - Increasing the length limit of a `varchar` column (or removing the limit)
286
- - Changing a `varchar` column to a `text` column
287
- - Changing a `text` column to a `varchar` column with no length limit
288
- - Increasing the precision of a `decimal` or `numeric` column
289
- - Making a `decimal` or `numeric` column unconstrained
290
- - Changing between `timestamp` and `timestamptz` columns when session time zone is UTC in PostgreSQL 12+
289
+ Type | Safe Changes
290
+ --- | ---
291
+ `bit` | Changing to `bit_varying`
292
+ `bit_varying` | Increasing or removing `:limit`
293
+ `cidr` | Changing to `inet`
294
+ `citext` | Changing to `text` if not indexed, changing to `string` with no `:limit` if not indexed
295
+ `datetime` | Increasing or removing `:precision`, changing to `timestamptz` when session time zone is UTC in PostgreSQL 12+
296
+ `decimal` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
297
+ `interval` | Increasing or removing `:precision`
298
+ `numeric` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
299
+ `string` | Increasing or removing `:limit`, changing to `text`, changing to `citext` if not indexed
300
+ `text` | Changing to `string` with no `:limit`, changing to `citext` if not indexed
301
+ `timestamptz` | Increasing or removing `:limit`, changing to `datetime` when session time zone is UTC in PostgreSQL 12+
302
+ `xml` | Changing to `text`, changing to `string` with no `:limit`
291
303
 
292
304
  :white_check_mark: **Good**
293
305
 
@@ -1292,6 +1304,33 @@ Background migrations:
1292
1304
  - add UI
1293
1305
  - support batching over non-integer and multiple columns
1294
1306
 
1307
+ ## Comparison to `strong_migrations`
1308
+
1309
+ This gem was heavily inspired by the `strong_migrations` and GitLab's approaches to database migrations. This gem is a superset of `strong_migrations`, feature-wise, and has the same APIs.
1310
+
1311
+ The main differences are:
1312
+
1313
+ 1. `strong_migrations` provides you **text guidance** on how to run migrations safer and you should implement them yourself. This new gem has actual [**code helpers**](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/schema_statements.rb) (and suggests them when fails on unsafe migrations) you can use to do what you want. See [example](#example) for an example.
1314
+
1315
+ It has migrations helpers for:
1316
+
1317
+ * renaming tables/columns
1318
+ * changing columns types (including changing primary/foreign keys from `integer` to `bigint`)
1319
+ * adding columns with default values
1320
+ * backfilling data
1321
+ * adding different types of constraints
1322
+ * and others
1323
+
1324
+ 2. This gem has a [powerful internal framework](https://github.com/fatkodima/online_migrations/blob/master/BACKGROUND_MIGRATIONS.md) for running data migrations on very large tables using background migrations.
1325
+
1326
+ For example, you can use background migrations to migrate data that’s stored in a single JSON column to a separate table instead; backfill values from one column to another (as one of the steps when changing column type); or backfill some column’s value from an API.
1327
+
1328
+ 3. Yet, it has more checks for unsafe changes (see [checks](#checks)).
1329
+
1330
+ 4. Currently, this gem supports only PostgreSQL, while `strong_migrations` also checks `MySQL` and `MariaDB` migrations.
1331
+
1332
+ 5. This gem is more flexible in terms of configuration - see [config file](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/config.rb) for additional configuration options.
1333
+
1295
1334
  ## License
1296
1335
 
1297
1336
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -20,9 +20,9 @@ module OnlineMigrations
20
20
  # Otherwise, the SQL is `WHERE column != value`. This condition ignores column
21
21
  # with NULLs in it, so we need to also manually check for NULLs.
22
22
  quoted_column = connection.quote_column_name(column)
23
- model.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
23
+ model.unscoped.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
24
24
  else
25
- Utils.ar_where_not_multiple_conditions(model, updates)
25
+ Utils.ar_where_not_multiple_conditions(model.unscoped, updates)
26
26
  end
27
27
  end
28
28
 
@@ -42,7 +42,7 @@ module OnlineMigrations
42
42
  @model ||= if model_name.present?
43
43
  Object.const_get(model_name, false)
44
44
  else
45
- Utils.define_model(ActiveRecord::Base.connection, table_name)
45
+ Utils.define_model(table_name)
46
46
  end
47
47
  end
48
48
 
@@ -26,6 +26,7 @@ module OnlineMigrations
26
26
 
27
27
  def relation
28
28
  relation = model
29
+ .unscoped
29
30
  .where(copy_to.map { |to_column| [to_column, nil] }.to_h)
30
31
 
31
32
  Utils.ar_where_not_multiple_conditions(
@@ -78,7 +79,7 @@ module OnlineMigrations
78
79
  @model ||= if model_name.present?
79
80
  Object.const_get(model_name, false)
80
81
  else
81
- Utils.define_model(ActiveRecord::Base.connection, table_name)
82
+ Utils.define_model(table_name)
82
83
  end
83
84
  end
84
85
 
@@ -27,10 +27,11 @@ module OnlineMigrations
27
27
 
28
28
  validates :migration_name, :batch_column_name, presence: true
29
29
 
30
- validates :batch_pause, :min_value, :max_value, :batch_size, :sub_batch_size,
30
+ validates :min_value, :max_value, :batch_size, :sub_batch_size,
31
31
  presence: true, numericality: { greater_than: 0 }
32
32
 
33
- validates :sub_batch_pause_ms, presence: true, numericality: { greater_than_or_equal_to: 0 }
33
+ validates :batch_pause, :sub_batch_pause_ms, presence: true,
34
+ numericality: { greater_than_or_equal_to: 0 }
34
35
  validates :rows_count, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
35
36
  validates :arguments, uniqueness: { scope: :migration_name }
36
37
 
@@ -166,6 +166,45 @@ module OnlineMigrations
166
166
  )
167
167
  end
168
168
 
169
+ # Resets one or more counter caches to their correct value using background migrations.
170
+ # This is useful when adding new counter caches, or if the counter has been corrupted or modified directly by SQL.
171
+ #
172
+ # @param model_name [String]
173
+ # @param counters [Array]
174
+ # @param touch [Boolean, Symbol, Array] touch timestamp columns when updating.
175
+ # - when `true` - will touch `updated_at` and/or `updated_on`
176
+ # - when `Symbol` or `Array` - will touch specific column(s)
177
+ # @param options [Hash] used to control the behavior of background migration.
178
+ # See `#enqueue_background_migration`
179
+ #
180
+ # @return [OnlineMigrations::BackgroundMigrations::Migration]
181
+ #
182
+ # @example
183
+ # reset_counters_in_background("User", :projects, :friends, touch: true)
184
+ #
185
+ # @example Touch specific column
186
+ # reset_counters_in_background("User", :projects, touch: :touched_at)
187
+ #
188
+ # @example Touch with specific time value
189
+ # reset_counters_in_background("User", :projects, touch: [time: 2.days.ago])
190
+ #
191
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html#method-i-reset_counters
192
+ #
193
+ # @note This method is better suited for extra large tables (100s of millions of records).
194
+ # For smaller tables it is probably better and easier to use `reset_counters` from the ActiveRecord.
195
+ #
196
+ def reset_counters_in_background(model_name, *counters, touch: nil, **options)
197
+ model_name = model_name.name if model_name.is_a?(Class)
198
+
199
+ enqueue_background_migration(
200
+ "ResetCounters",
201
+ model_name,
202
+ counters,
203
+ { touch: touch },
204
+ **options
205
+ )
206
+ end
207
+
169
208
  # Creates a background migration for the given job class name.
170
209
  #
171
210
  # A background migration runs one job at a time, computing the bounds of the next batch
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ module BackgroundMigrations
5
+ # @private
6
+ class ResetCounters < BackgroundMigration
7
+ attr_reader :model, :counters, :touch
8
+
9
+ def initialize(model_name, counters, options = {})
10
+ @model = Object.const_get(model_name, false)
11
+ @counters = counters
12
+ @touch = options[:touch]
13
+ end
14
+
15
+ def relation
16
+ model.unscoped
17
+ end
18
+
19
+ def process_batch(relation)
20
+ updates = counters.map do |counter_association|
21
+ has_many_association = has_many_association(counter_association)
22
+
23
+ foreign_key = has_many_association.foreign_key.to_s
24
+ child_class = has_many_association.klass
25
+ reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
26
+ counter_name = reflection.counter_cache_column
27
+
28
+ quoted_association_table = connection.quote_table_name(has_many_association.table_name)
29
+ count_subquery = <<-SQL.strip_heredoc
30
+ SELECT COUNT(*)
31
+ FROM #{quoted_association_table}
32
+ WHERE #{quoted_association_table}.#{connection.quote_column_name(foreign_key)} =
33
+ #{model.quoted_table_name}.#{model.quoted_primary_key}
34
+ SQL
35
+
36
+ "#{connection.quote_column_name(counter_name)} = (#{count_subquery})"
37
+ end
38
+
39
+ if touch
40
+ names = touch if touch != true
41
+ names = Array.wrap(names)
42
+ options = names.extract_options!
43
+ touch_updates = touch_attributes_with_time(*names, **options)
44
+ # In ActiveRecord 4.2 sanitize_sql_for_assignment is protected
45
+ updates << model.send(:sanitize_sql_for_assignment, touch_updates)
46
+ end
47
+
48
+ relation.update_all(updates.join(", "))
49
+ end
50
+
51
+ def count
52
+ # Exact counts are expensive on large tables, since PostgreSQL
53
+ # needs to do a full scan. An estimated count should give a pretty decent
54
+ # approximation of rows count in this case.
55
+ Utils.estimated_count(connection, model.table_name)
56
+ end
57
+
58
+ private
59
+ def has_many_association(counter_association) # rubocop:disable Naming/PredicateName
60
+ has_many_association = model.reflect_on_association(counter_association)
61
+
62
+ unless has_many_association
63
+ has_many = model.reflect_on_all_associations(:has_many)
64
+
65
+ has_many_association = has_many.find do |association|
66
+ counter_cache_column = association.counter_cache_column
67
+
68
+ # ActiveRecord <= 4.2 is able to return only explicitly provided `counter_cache` column.
69
+ if !counter_cache_column && Utils.ar_version <= 4.2
70
+ counter_cache_column = "#{association.name}_count"
71
+ end
72
+ counter_cache_column && counter_cache_column.to_sym == counter_association.to_sym
73
+ end
74
+
75
+ counter_association = has_many_association.plural_name if has_many_association
76
+ end
77
+ raise ArgumentError, "'#{model.name}' has no association called '#{counter_association}'" unless has_many_association
78
+
79
+ if has_many_association.is_a?(ActiveRecord::Reflection::ThroughReflection)
80
+ has_many_association = has_many_association.through_reflection
81
+ end
82
+
83
+ has_many_association
84
+ end
85
+
86
+ def touch_attributes_with_time(*names, time: nil)
87
+ attribute_names = timestamp_attributes_for_update & model.column_names
88
+ attribute_names |= names.map(&:to_s)
89
+ attribute_names.map { |attribute_name| [attribute_name, time || Time.current] }.to_h
90
+ end
91
+
92
+ def timestamp_attributes_for_update
93
+ ["updated_at", "updated_on"].map { |name| model.attribute_aliases[name] || name }
94
+ end
95
+
96
+ def connection
97
+ model.connection
98
+ end
99
+ end
100
+ end
101
+ end
@@ -110,7 +110,7 @@ module OnlineMigrations
110
110
  column_options = options[column_name] || {}
111
111
  tmp_column_name = conversions[column_name]
112
112
 
113
- if @connection.server_version >= 11_00_00 &&
113
+ if __raw_connection.server_version >= 11_00_00 &&
114
114
  primary_key(table_name) == column_name.to_s && old_col.type == :integer
115
115
  # If the column to be converted is a Primary Key, set it to
116
116
  # `NOT NULL DEFAULT 0` and we'll copy the correct values when backfilling.
@@ -27,6 +27,7 @@ module OnlineMigrations
27
27
  end
28
28
 
29
29
  def check(command, *args, &block)
30
+ check_database_version
30
31
  check_lock_timeout
31
32
 
32
33
  unless safe?
@@ -43,6 +44,22 @@ module OnlineMigrations
43
44
  end
44
45
 
45
46
  private
47
+ def check_database_version
48
+ return if defined?(@database_version_checked)
49
+
50
+ adapter = connection.adapter_name
51
+ case adapter
52
+ when /postg/i
53
+ if postgresql_version < Gem::Version.new("9.6")
54
+ raise "#{adapter} < 9.6 is not supported"
55
+ end
56
+ else
57
+ raise "#{adapter} is not supported"
58
+ end
59
+
60
+ @database_version_checked = true
61
+ end
62
+
46
63
  def check_lock_timeout
47
64
  limit = OnlineMigrations.config.lock_timeout_limit
48
65
 
@@ -206,10 +223,21 @@ module OnlineMigrations
206
223
 
207
224
  type = type.to_sym
208
225
 
209
- existing_column = connection.columns(table_name).find { |c| c.name == column_name.to_s }
226
+ existing_column = column_for(table_name, column_name)
210
227
  if existing_column
211
228
  existing_type = existing_column.type.to_sym
212
229
 
230
+ # To get a list of binary-coercible types:
231
+ #
232
+ # SELECT stype.typname AS source, ttype.typname AS target
233
+ # FROM pg_cast
234
+ # INNER JOIN pg_type stype ON pg_cast.castsource = stype.oid
235
+ # INNER JOIN pg_type ttype ON pg_cast.casttarget = ttype.oid
236
+ # WHERE castmethod = 'b'
237
+ # ORDER BY 1, 2
238
+
239
+ # https://www.postgresql.org/docs/release/9.2.0/#AEN124164
240
+
213
241
  safe =
214
242
  case type
215
243
  when :string
@@ -218,12 +246,25 @@ module OnlineMigrations
218
246
  case existing_type
219
247
  when :string
220
248
  !options[:limit] || (existing_column.limit && options[:limit] >= existing_column.limit)
221
- when :text
249
+ when :text, :xml
222
250
  !options[:limit]
251
+ when :citext
252
+ !options[:limit] && !indexed?(table_name, column_name)
223
253
  end
224
254
  when :text
225
- # safe to change varchar to text (and text to text)
226
- [:string, :text].include?(existing_type)
255
+ [:string, :text, :xml].include?(existing_type) ||
256
+ (existing_type == :citext && !indexed?(table_name, column_name))
257
+ when :citext
258
+ [:string, :text].include?(existing_type) && !indexed?(table_name, column_name)
259
+ when :bit_varying
260
+ case existing_type
261
+ when :bit
262
+ !options[:limit]
263
+ when :bit_varying
264
+ # safe to increase limit or remove it
265
+ # not safe to decrease limit or add a limit
266
+ !options[:limit] || (existing_column.limit && options[:limit] >= existing_column.limit)
267
+ end
227
268
  when :numeric, :decimal
228
269
  # numeric and decimal are equivalent and can be used interchangably
229
270
  [:numeric, :decimal].include?(existing_type) &&
@@ -239,9 +280,25 @@ module OnlineMigrations
239
280
  )
240
281
  )
241
282
  when :datetime, :timestamp, :timestamptz
242
- [:timestamp, :timestamptz].include?(existing_type) &&
243
- postgresql_version >= Gem::Version.new("12") &&
244
- connection.select_value("SHOW timezone") == "UTC"
283
+ # precision for datetime
284
+ # limit for timestamp, timestamptz
285
+ precision = (type == :datetime ? options[:precision] : options[:limit]) || 6
286
+ existing_precision = existing_column.precision || existing_column.limit || 6
287
+
288
+ [:datetime, :timestamp, :timestamptz].include?(existing_type) &&
289
+ precision >= existing_precision &&
290
+ (type == existing_type ||
291
+ (postgresql_version >= Gem::Version.new("12") &&
292
+ connection.select_value("SHOW timezone") == "UTC"))
293
+ when :interval
294
+ precision = options[:precision] || options[:limit] || 6
295
+ existing_precision = existing_column.precision || existing_column.limit || 6
296
+
297
+ # PostgreSQL interval data type was added in https://github.com/rails/rails/pull/16919
298
+ (existing_type == :interval || (Utils.ar_version < 6.1 && existing_column.sql_type.start_with?("interval"))) &&
299
+ precision >= existing_precision
300
+ when :inet
301
+ existing_type == :cidr
245
302
  else
246
303
  type == existing_type &&
247
304
  options[:limit] == existing_column.limit &&
@@ -281,15 +338,13 @@ module OnlineMigrations
281
338
  constraint_name = "#{table_name}_#{column_name}_null"
282
339
  vars = {
283
340
  add_constraint_code: command_str(:add_not_null_constraint, table_name, column_name, name: constraint_name, validate: false),
284
- backfill_code: nil,
285
341
  validate_constraint_code: command_str(:validate_not_null_constraint, table_name, column_name, name: constraint_name),
286
342
  remove_constraint_code: nil,
343
+ table_name: table_name,
344
+ column_name: column_name,
345
+ default: default,
287
346
  }
288
347
 
289
- if !default.nil?
290
- vars[:backfill_code] = command_str(:update_column_in_batches, table_name, column_name, default)
291
- end
292
-
293
348
  if postgresql_version >= Gem::Version.new("12")
294
349
  vars[:remove_constraint_code] = command_str(:remove_check_constraint, table_name, name: constraint_name)
295
350
  vars[:change_column_null_code] = command_str(:change_column_null, table_name, column_name, false)
@@ -537,7 +592,7 @@ module OnlineMigrations
537
592
  target_version.to_s
538
593
  else
539
594
  # For rails 6.0+ we can use connection.database_version
540
- pg_connection = connection.instance_variable_get(:@connection)
595
+ pg_connection = connection.send(:__raw_connection)
541
596
  database_version = pg_connection.server_version
542
597
  patch = database_version % 100
543
598
  database_version /= 100
@@ -669,6 +724,10 @@ module OnlineMigrations
669
724
  [:integer, :bigint, :serial, :bigserial, :uuid].include?(type)
670
725
  end
671
726
 
727
+ def indexed?(table_name, column_name)
728
+ connection.indexes(table_name).any? { |index| index.columns.include?(column_name.to_s) }
729
+ end
730
+
672
731
  def column_for(table_name, column_name)
673
732
  connection.columns(table_name).find { |column| column.name == column_name.to_s }
674
733
  end
@@ -214,11 +214,13 @@ class <%= migration_name %> < <%= migration_parent %>
214
214
 
215
215
  def change
216
216
  <%= add_constraint_code %>
217
- <% if backfill_code %>
217
+ <% unless default.nil? %>
218
218
 
219
219
  # Passing a default value to change_column_null runs a single UPDATE query,
220
220
  # which can cause downtime. Instead, backfill the existing rows in batches.
221
- <%= backfill_code %>
221
+ update_column_in_batches(:<%= table_name %>, :<%= column_name %>, <%= default.inspect %>) do |relation|
222
+ relation.where(<%= column_name %>: nil)
223
+ end
222
224
 
223
225
  <% end %>
224
226
  <%= validate_constraint_code %>
@@ -81,7 +81,7 @@ module OnlineMigrations
81
81
  end
82
82
  end
83
83
 
84
- model = Utils.define_model(self, table_name)
84
+ model = Utils.define_model(table_name, self)
85
85
 
86
86
  conditions = columns_and_values.map do |(column_name, value)|
87
87
  value = Arel.sql(value.call.to_s) if value.is_a?(Proc)
@@ -396,7 +396,7 @@ module OnlineMigrations
396
396
  raise ArgumentError, "Expressions as default are not supported"
397
397
  end
398
398
 
399
- if @connection.server_version >= 11_00_00 && !Utils.volatile_default?(self, type, default)
399
+ if __raw_connection.server_version >= 11_00_00 && !Utils.volatile_default?(self, type, default)
400
400
  add_column(table_name, column_name, type, **options)
401
401
  else
402
402
  __ensure_not_in_transaction!
@@ -422,7 +422,7 @@ module OnlineMigrations
422
422
  add_not_null_constraint(table_name, column_name, validate: false)
423
423
  validate_not_null_constraint(table_name, column_name)
424
424
 
425
- if @connection.server_version >= 12_00_00
425
+ if __raw_connection.server_version >= 12_00_00
426
426
  # In PostgreSQL 12+ it is safe to "promote" a CHECK constraint to `NOT NULL` for the column
427
427
  change_column_null(table_name, column_name, false)
428
428
  remove_not_null_constraint(table_name, column_name)
@@ -1046,5 +1046,14 @@ module OnlineMigrations
1046
1046
  _, schema = table_name.to_s.split(".").reverse
1047
1047
  schema ? quote(schema) : "current_schema()"
1048
1048
  end
1049
+
1050
+ def __raw_connection
1051
+ # ActiveRecord > 7.0.2.2 (https://github.com/rails/rails/pull/44530)
1052
+ if defined?(@raw_connection)
1053
+ @raw_connection
1054
+ else
1055
+ @connection
1056
+ end
1057
+ end
1049
1058
  end
1050
1059
  end
@@ -53,7 +53,7 @@ module OnlineMigrations
53
53
  end
54
54
  end
55
55
 
56
- def define_model(connection, table_name)
56
+ def define_model(table_name, connection = ActiveRecord::Base.connection)
57
57
  Class.new(ActiveRecord::Base) do
58
58
  self.table_name = table_name
59
59
  self.inheritance_column = :_type_disabled
@@ -101,7 +101,14 @@ module OnlineMigrations
101
101
  WHERE relname = #{quoted_table}
102
102
  AND relnamespace = current_schema()::regnamespace
103
103
  SQL
104
- count.to_i if count
104
+
105
+ if count
106
+ count = count.to_i
107
+ # If the table has never yet been vacuumed or analyzed, reltuples contains -1
108
+ # indicating that the row count is unknown.
109
+ count = 0 if count == -1
110
+ count
111
+ end
105
112
  end
106
113
 
107
114
  def ar_where_not_multiple_conditions(relation, conditions)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -1,46 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
-
5
- require "online_migrations/utils"
6
- require "online_migrations/error_messages"
7
- require "online_migrations/config"
8
- require "online_migrations/batch_iterator"
9
- require "online_migrations/verbose_sql_logs"
10
- require "online_migrations/migration"
11
- require "online_migrations/migrator"
12
- require "online_migrations/database_tasks"
13
- require "online_migrations/foreign_key_definition"
14
- require "online_migrations/foreign_keys_collector"
15
- require "online_migrations/index_definition"
16
- require "online_migrations/indexes_collector"
17
- require "online_migrations/command_checker"
18
- require "online_migrations/schema_cache"
19
- require "online_migrations/background_migration"
20
- require "online_migrations/background_migrations/config"
21
- require "online_migrations/background_migrations/migration_status_validator"
22
- require "online_migrations/background_migrations/migration_job_status_validator"
23
- require "online_migrations/background_migrations/background_migration_class_validator"
24
- require "online_migrations/background_migrations/backfill_column"
25
- require "online_migrations/background_migrations/copy_column"
26
- require "online_migrations/background_migrations/migration_job"
27
- require "online_migrations/background_migrations/migration"
28
- require "online_migrations/background_migrations/migration_job_runner"
29
- require "online_migrations/background_migrations/migration_runner"
30
- require "online_migrations/background_migrations/migration_helpers"
31
- require "online_migrations/background_migrations/advisory_lock"
32
- require "online_migrations/background_migrations/scheduler"
33
- require "online_migrations/lock_retrier"
34
- require "online_migrations/command_recorder"
35
- require "online_migrations/copy_trigger"
36
- require "online_migrations/change_column_type_helpers"
37
- require "online_migrations/schema_statements"
38
4
  require "online_migrations/version"
39
5
 
40
6
  module OnlineMigrations
41
7
  class Error < StandardError; end
42
8
  class UnsafeMigration < Error; end
43
9
 
10
+ extend ActiveSupport::Autoload
11
+
12
+ autoload :Utils
13
+ autoload :ErrorMessages
14
+ autoload :Config
15
+ autoload :BatchIterator
16
+ autoload :VerboseSqlLogs
17
+ autoload :Migration
18
+ autoload :Migrator
19
+ autoload :DatabaseTasks
20
+ autoload :ForeignKeyDefinition
21
+ autoload :ForeignKeysCollector
22
+ autoload :IndexDefinition
23
+ autoload :IndexesCollector
24
+ autoload :CommandChecker
25
+ autoload :SchemaCache
26
+ autoload :BackgroundMigration
27
+
28
+ autoload_at "online_migrations/lock_retrier" do
29
+ autoload :LockRetrier
30
+ autoload :ConstantLockRetrier
31
+ autoload :ExponentialLockRetrier
32
+ autoload :NullLockRetrier
33
+ end
34
+
35
+ autoload :CommandRecorder
36
+ autoload :CopyTrigger
37
+ autoload :ChangeColumnTypeHelpers
38
+ autoload :SchemaStatements
39
+
40
+ module BackgroundMigrations
41
+ extend ActiveSupport::Autoload
42
+
43
+ autoload :Config
44
+ autoload :MigrationStatusValidator
45
+ autoload :MigrationJobStatusValidator
46
+ autoload :BackgroundMigrationClassValidator
47
+ autoload :BackfillColumn
48
+ autoload :CopyColumn
49
+ autoload :ResetCounters
50
+ autoload :MigrationJob
51
+ autoload :Migration
52
+ autoload :MigrationJobRunner
53
+ autoload :MigrationRunner
54
+ autoload :MigrationHelpers
55
+ autoload :AdvisoryLock
56
+ autoload :Scheduler
57
+ end
58
+
44
59
  class << self
45
60
  # @private
46
61
  attr_accessor :current_migration
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: online_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-10 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -31,25 +31,10 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
- - ".github/workflows/test.yml"
35
- - ".gitignore"
36
- - ".rubocop.yml"
37
- - ".yardopts"
38
34
  - BACKGROUND_MIGRATIONS.md
39
35
  - CHANGELOG.md
40
- - Gemfile
41
- - Gemfile.lock
42
36
  - LICENSE.txt
43
37
  - README.md
44
- - Rakefile
45
- - gemfiles/activerecord_42.gemfile
46
- - gemfiles/activerecord_50.gemfile
47
- - gemfiles/activerecord_51.gemfile
48
- - gemfiles/activerecord_52.gemfile
49
- - gemfiles/activerecord_60.gemfile
50
- - gemfiles/activerecord_61.gemfile
51
- - gemfiles/activerecord_70.gemfile
52
- - gemfiles/activerecord_head.gemfile
53
38
  - lib/generators/online_migrations/background_migration_generator.rb
54
39
  - lib/generators/online_migrations/install_generator.rb
55
40
  - lib/generators/online_migrations/templates/background_migration.rb.tt
@@ -69,6 +54,7 @@ files:
69
54
  - lib/online_migrations/background_migrations/migration_job_status_validator.rb
70
55
  - lib/online_migrations/background_migrations/migration_runner.rb
71
56
  - lib/online_migrations/background_migrations/migration_status_validator.rb
57
+ - lib/online_migrations/background_migrations/reset_counters.rb
72
58
  - lib/online_migrations/background_migrations/scheduler.rb
73
59
  - lib/online_migrations/batch_iterator.rb
74
60
  - lib/online_migrations/change_column_type_helpers.rb
@@ -90,7 +76,6 @@ files:
90
76
  - lib/online_migrations/utils.rb
91
77
  - lib/online_migrations/verbose_sql_logs.rb
92
78
  - lib/online_migrations/version.rb
93
- - online_migrations.gemspec
94
79
  homepage: https://github.com/fatkodima/online_migrations
95
80
  licenses:
96
81
  - MIT
@@ -1,112 +0,0 @@
1
- name: Test
2
- on: [push, pull_request]
3
-
4
- jobs:
5
- # Run the linter first for rapid feedback if some trivial stylistic issues
6
- # slipped through the cracks.
7
- lint:
8
- runs-on: ubuntu-latest
9
- steps:
10
- - uses: actions/checkout@v2
11
- - uses: ruby/setup-ruby@v1
12
- with:
13
- ruby-version: 3.1.0
14
- bundler-cache: true
15
- - run: bundle exec rubocop
16
-
17
- test:
18
- needs: lint
19
- runs-on: ubuntu-latest
20
- services:
21
- postgres:
22
- image: postgres
23
- env:
24
- POSTGRES_DB: online_migrations_test
25
- POSTGRES_PASSWORD: postgres
26
- options: >-
27
- --health-cmd pg_isready
28
- --health-interval 10s
29
- --health-timeout 5s
30
- --health-retries 5
31
- ports:
32
- - 5432:5432
33
- strategy:
34
- matrix:
35
- include:
36
- - ruby-version: 2.4.10
37
- gemfile: gemfiles/activerecord_42.gemfile
38
- - ruby-version: 2.5.9
39
- gemfile: gemfiles/activerecord_42.gemfile
40
- - ruby-version: 2.6.9
41
- gemfile: gemfiles/activerecord_42.gemfile
42
-
43
- - ruby-version: 2.4.10
44
- gemfile: gemfiles/activerecord_50.gemfile
45
- - ruby-version: 2.5.9
46
- gemfile: gemfiles/activerecord_50.gemfile
47
- - ruby-version: 2.6.9
48
- gemfile: gemfiles/activerecord_50.gemfile
49
- - ruby-version: 2.7.5
50
- gemfile: gemfiles/activerecord_50.gemfile
51
-
52
- - ruby-version: 2.4.10
53
- gemfile: gemfiles/activerecord_51.gemfile
54
- - ruby-version: 2.5.9
55
- gemfile: gemfiles/activerecord_51.gemfile
56
- - ruby-version: 2.6.9
57
- gemfile: gemfiles/activerecord_51.gemfile
58
- - ruby-version: 2.7.5
59
- gemfile: gemfiles/activerecord_51.gemfile
60
-
61
- - ruby-version: 2.4.10
62
- gemfile: gemfiles/activerecord_52.gemfile
63
- - ruby-version: 2.5.9
64
- gemfile: gemfiles/activerecord_52.gemfile
65
- - ruby-version: 2.6.9
66
- gemfile: gemfiles/activerecord_52.gemfile
67
- - ruby-version: 2.7.5
68
- gemfile: gemfiles/activerecord_52.gemfile
69
-
70
- - ruby-version: 2.5.9
71
- gemfile: gemfiles/activerecord_60.gemfile
72
- - ruby-version: 2.6.9
73
- gemfile: gemfiles/activerecord_60.gemfile
74
- - ruby-version: 2.7.5
75
- gemfile: gemfiles/activerecord_60.gemfile
76
- - ruby-version: 3.0.3
77
- gemfile: gemfiles/activerecord_60.gemfile
78
-
79
- - ruby-version: 2.5.9
80
- gemfile: gemfiles/activerecord_61.gemfile
81
- - ruby-version: 2.6.9
82
- gemfile: gemfiles/activerecord_61.gemfile
83
- - ruby-version: 2.7.4
84
- gemfile: gemfiles/activerecord_61.gemfile
85
- - ruby-version: 3.0.3
86
- gemfile: gemfiles/activerecord_61.gemfile
87
- - ruby-version: 3.1.0
88
- gemfile: gemfiles/activerecord_61.gemfile
89
-
90
- - ruby-version: 2.7.4
91
- gemfile: gemfiles/activerecord_70.gemfile
92
- - ruby-version: 3.0.3
93
- gemfile: gemfiles/activerecord_70.gemfile
94
- - ruby-version: 3.1.0
95
- gemfile: gemfiles/activerecord_70.gemfile
96
-
97
- - ruby-version: 2.7.4
98
- gemfile: gemfiles/activerecord_head.gemfile
99
- - ruby-version: 3.0.3
100
- gemfile: gemfiles/activerecord_head.gemfile
101
- - ruby-version: 3.1.0
102
- gemfile: gemfiles/activerecord_head.gemfile
103
- env:
104
- BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
105
- steps:
106
- - uses: actions/checkout@v2
107
- - uses: ruby/setup-ruby@v1
108
- with:
109
- ruby-version: ${{ matrix.ruby-version }}
110
- bundler-cache: true
111
- - name: Run the test suite
112
- run: bundle exec rake test
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- gemfiles/**.lock
11
- /debug.log
data/.rubocop.yml DELETED
@@ -1,120 +0,0 @@
1
- AllCops:
2
- NewCops: enable
3
- SuggestExtensions: false
4
-
5
- Style/StringLiterals:
6
- EnforcedStyle: double_quotes
7
-
8
- Style/RescueModifier:
9
- Exclude:
10
- - test/**/*
11
-
12
- Style/IfUnlessModifier:
13
- Enabled: false
14
-
15
- Style/SymbolArray:
16
- EnforcedStyle: brackets
17
-
18
- Style/WordArray:
19
- EnforcedStyle: brackets
20
-
21
- Style/GuardClause:
22
- Enabled: false
23
-
24
- Style/Documentation:
25
- Enabled: false
26
-
27
- Style/MutableConstant:
28
- Enabled: false
29
-
30
- Style/MissingRespondToMissing:
31
- Enabled: false
32
-
33
- Style/TrailingCommaInHashLiteral:
34
- EnforcedStyleForMultiline: comma
35
-
36
- Style/TrailingCommaInArrayLiteral:
37
- EnforcedStyleForMultiline: comma
38
-
39
- Style/NumericPredicate:
40
- Enabled: false
41
-
42
- Style/NegatedIf:
43
- Enabled: false
44
-
45
- Style/ConditionalAssignment:
46
- Enabled: false
47
-
48
- # only for ruby 2.3+
49
- Style/SafeNavigation:
50
- Enabled: false
51
-
52
- Style/NumericLiterals:
53
- Enabled: false
54
-
55
- Style/Next:
56
- Enabled: false
57
-
58
- Style/GlobalVars:
59
- Exclude:
60
- - test/**/*
61
-
62
- Style/Lambda:
63
- EnforcedStyle: literal
64
-
65
- Style/WhileUntilModifier:
66
- Enabled: false
67
-
68
- Style/HashAsLastArrayItem:
69
- Enabled: false
70
-
71
- # only for ruby 2.6+
72
- Style/MapToHash:
73
- Enabled: false
74
-
75
- # we can not use new syntax for older rubies
76
- Lint/ErbNewArguments:
77
- Enabled: false
78
-
79
- Lint/MissingSuper:
80
- Enabled: false
81
-
82
- Layout/EmptyLinesAroundAccessModifier:
83
- EnforcedStyle: only_before
84
-
85
- Layout/IndentationConsistency:
86
- EnforcedStyle: indented_internal_methods
87
-
88
- Layout/ArgumentAlignment:
89
- Enabled: false
90
-
91
- Layout/LineLength:
92
- Enabled: false
93
-
94
- Layout/MultilineMethodCallIndentation:
95
- Enabled: false
96
-
97
- Layout/HashAlignment:
98
- Enabled: false
99
-
100
- Naming/VariableNumber:
101
- Exclude:
102
- - test/**/*
103
-
104
- Naming/MethodParameterName:
105
- AllowedNames:
106
- - of
107
- - fk
108
-
109
- Naming/AccessorMethodName:
110
- Exclude:
111
- - test/**/*
112
-
113
- Gemspec/RequiredRubyVersion:
114
- Enabled: false
115
-
116
- Gemspec/RequireMFA:
117
- Enabled: false
118
-
119
- Metrics:
120
- Enabled: false
data/.yardopts DELETED
@@ -1 +0,0 @@
1
- --no-private --markup markdown lib/**/**.rb - README.md
data/Gemfile DELETED
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in online_migrations.gemspec
6
- gemspec
7
-
8
- gem "minitest", "~> 5.0"
9
- gem "rake", "~> 12.0"
10
- gem "yard"
11
-
12
- if defined?(@ar_gem_requirement)
13
- gem "activerecord", @ar_gem_requirement
14
- gem "railties", @ar_gem_requirement
15
- else
16
- gem "activerecord" # latest
17
- gem "railties" # to test generator
18
-
19
- # Run Rubocop only on latest rubies, because it is incompatible with older versions.
20
- gem "rubocop", "~> 1.24"
21
- end
22
-
23
- if defined?(@pg_gem_requirement)
24
- gem "pg", @pg_gem_requirement
25
- else
26
- gem "pg", "~> 1.2"
27
- end
data/Gemfile.lock DELETED
@@ -1,108 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- online_migrations (0.3.0)
5
- activerecord (>= 4.2)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- actionpack (7.0.2)
11
- actionview (= 7.0.2)
12
- activesupport (= 7.0.2)
13
- rack (~> 2.0, >= 2.2.0)
14
- rack-test (>= 0.6.3)
15
- rails-dom-testing (~> 2.0)
16
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
17
- actionview (7.0.2)
18
- activesupport (= 7.0.2)
19
- builder (~> 3.1)
20
- erubi (~> 1.4)
21
- rails-dom-testing (~> 2.0)
22
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
23
- activemodel (7.0.2)
24
- activesupport (= 7.0.2)
25
- activerecord (7.0.2)
26
- activemodel (= 7.0.2)
27
- activesupport (= 7.0.2)
28
- activesupport (7.0.2)
29
- concurrent-ruby (~> 1.0, >= 1.0.2)
30
- i18n (>= 1.6, < 2)
31
- minitest (>= 5.1)
32
- tzinfo (~> 2.0)
33
- ast (2.4.2)
34
- builder (3.2.4)
35
- concurrent-ruby (1.1.9)
36
- crass (1.0.6)
37
- erubi (1.10.0)
38
- i18n (1.9.1)
39
- concurrent-ruby (~> 1.0)
40
- loofah (2.13.0)
41
- crass (~> 1.0.2)
42
- nokogiri (>= 1.5.9)
43
- method_source (1.0.0)
44
- mini_portile2 (2.7.1)
45
- minitest (5.15.0)
46
- nokogiri (1.13.1)
47
- mini_portile2 (~> 2.7.0)
48
- racc (~> 1.4)
49
- parallel (1.21.0)
50
- parser (3.1.0.0)
51
- ast (~> 2.4.1)
52
- pg (1.3.1)
53
- racc (1.6.0)
54
- rack (2.2.3)
55
- rack-test (1.1.0)
56
- rack (>= 1.0, < 3)
57
- rails-dom-testing (2.0.3)
58
- activesupport (>= 4.2.0)
59
- nokogiri (>= 1.6)
60
- rails-html-sanitizer (1.4.2)
61
- loofah (~> 2.3)
62
- railties (7.0.2)
63
- actionpack (= 7.0.2)
64
- activesupport (= 7.0.2)
65
- method_source
66
- rake (>= 12.2)
67
- thor (~> 1.0)
68
- zeitwerk (~> 2.5)
69
- rainbow (3.1.1)
70
- rake (12.3.3)
71
- regexp_parser (2.2.0)
72
- rexml (3.2.5)
73
- rubocop (1.25.1)
74
- parallel (~> 1.10)
75
- parser (>= 3.1.0.0)
76
- rainbow (>= 2.2.2, < 4.0)
77
- regexp_parser (>= 1.8, < 3.0)
78
- rexml
79
- rubocop-ast (>= 1.15.1, < 2.0)
80
- ruby-progressbar (~> 1.7)
81
- unicode-display_width (>= 1.4.0, < 3.0)
82
- rubocop-ast (1.15.1)
83
- parser (>= 3.0.1.1)
84
- ruby-progressbar (1.11.0)
85
- thor (1.2.1)
86
- tzinfo (2.0.4)
87
- concurrent-ruby (~> 1.0)
88
- unicode-display_width (2.1.0)
89
- webrick (1.7.0)
90
- yard (0.9.27)
91
- webrick (~> 1.7.0)
92
- zeitwerk (2.5.4)
93
-
94
- PLATFORMS
95
- ruby
96
-
97
- DEPENDENCIES
98
- activerecord
99
- minitest (~> 5.0)
100
- online_migrations!
101
- pg (~> 1.2)
102
- railties
103
- rake (~> 12.0)
104
- rubocop (~> 1.24)
105
- yard
106
-
107
- BUNDLED WITH
108
- 2.1.4
data/Rakefile DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
10
- end
11
-
12
- require "rdoc/task"
13
-
14
- RDoc::Task.new(:rdoc) do |rdoc|
15
- rdoc.rdoc_dir = "rdoc"
16
- rdoc.title = "OnlineMigrations"
17
- rdoc.options << "--line-numbers"
18
- rdoc.rdoc_files.include("README.md")
19
- rdoc.rdoc_files.include("BACKGROUND_MIGRATIONS.md")
20
- rdoc.rdoc_files.include("lib/**/*.rb")
21
- end
22
-
23
- task default: :test
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 4.2.0"
4
- @pg_gem_requirement = "< 1"
5
-
6
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 5.0.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 5.1.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 5.2.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 6.0.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 6.1.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = "~> 7.0.0"
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- @ar_gem_requirement = { github: "rails/rails", branch: "main" }
4
-
5
- eval_gemfile "../Gemfile"
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/online_migrations/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "online_migrations"
7
- spec.version = OnlineMigrations::VERSION
8
- spec.authors = ["fatkodima"]
9
- spec.email = ["fatkodima123@gmail.com"]
10
-
11
- spec.summary = "Catch unsafe PostgreSQL migrations in development and run them easier in production"
12
- spec.homepage = "https://github.com/fatkodima/online_migrations"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = Gem::Requirement.new(">= 2.1.0")
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = spec.homepage
18
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
- end
25
- spec.require_paths = ["lib"]
26
-
27
- spec.add_dependency "activerecord", ">= 4.2"
28
- end