strong_migrations 0.4.1 → 0.4.2
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +28 -29
- data/lib/strong_migrations.rb +1 -2
- data/lib/strong_migrations/checker.rb +18 -5
- data/lib/strong_migrations/migration.rb +9 -8
- data/lib/strong_migrations/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a77b960928e0167e1842dbd337dc0e47ecae009fec7e6b13cdfba539cb2addc5
|
4
|
+
data.tar.gz: 154e03c69d7790a81051bf55b0d2a04a0a1ce0ed83d347f5a44f7255f950f794
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90839d0e1af71af600a220dd689262c6472190d437a61e97da40bd79c7ec5f4d418d45c2b3ff88514e3ab1ceca8cc04bb912f1f3e6553ba3efac53eb6d171992
|
7
|
+
data.tar.gz: bbf6f7b9645391d8fdaa54b8edf59904d2f75c806bcd7d0940eb762cc102c2355fafa231114d20a1cb8cae603eea807fd654868da3f6f40846a8fe022bd58778
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -50,7 +50,7 @@ Also checks for best practices:
|
|
50
50
|
ActiveRecord caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
53
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
|
54
54
|
def change
|
55
55
|
remove_column :users, :some_column
|
56
56
|
end
|
@@ -71,7 +71,7 @@ end
|
|
71
71
|
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
72
72
|
|
73
73
|
```ruby
|
74
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[
|
74
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
|
75
75
|
def change
|
76
76
|
safety_assured { remove_column :users, :some_column }
|
77
77
|
end
|
@@ -87,7 +87,7 @@ end
|
|
87
87
|
Adding a column with a default value to an existing table causes the entire table to be rewritten.
|
88
88
|
|
89
89
|
```ruby
|
90
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
90
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
91
91
|
def change
|
92
92
|
add_column :users, :some_column, :text, default: "default_value"
|
93
93
|
end
|
@@ -101,7 +101,7 @@ end
|
|
101
101
|
Instead, add the column without a default value, then change the default.
|
102
102
|
|
103
103
|
```ruby
|
104
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
104
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
105
105
|
def up
|
106
106
|
add_column :users, :some_column, :text
|
107
107
|
change_column_default :users, :some_column, "default_value"
|
@@ -122,7 +122,7 @@ See the next section for how to backfill.
|
|
122
122
|
Backfilling in the same transaction that alters a table locks the table for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
123
123
|
|
124
124
|
```ruby
|
125
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
125
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
126
126
|
def change
|
127
127
|
add_column :users, :some_column, :text
|
128
128
|
User.update_all some_column: "default_value"
|
@@ -134,10 +134,10 @@ Also, running a single query to update data can cause issues for large tables.
|
|
134
134
|
|
135
135
|
#### Good
|
136
136
|
|
137
|
-
There are three keys: batching, throttling, and running it outside a transaction. Use the Rails console or a separate migration with `disable_ddl_transaction!`.
|
137
|
+
There are three keys to backfilling safely: batching, throttling, and running it outside a transaction. Use the Rails console or a separate migration with `disable_ddl_transaction!`.
|
138
138
|
|
139
139
|
```ruby
|
140
|
-
class BackfillSomeColumn < ActiveRecord::Migration[
|
140
|
+
class BackfillSomeColumn < ActiveRecord::Migration[6.0]
|
141
141
|
disable_ddl_transaction!
|
142
142
|
|
143
143
|
def change
|
@@ -156,7 +156,7 @@ end
|
|
156
156
|
In Postgres, adding a non-concurrent index locks the table.
|
157
157
|
|
158
158
|
```ruby
|
159
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
159
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
|
160
160
|
def change
|
161
161
|
add_index :users, :some_column
|
162
162
|
end
|
@@ -168,7 +168,7 @@ end
|
|
168
168
|
Add indexes concurrently.
|
169
169
|
|
170
170
|
```ruby
|
171
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
171
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
|
172
172
|
disable_ddl_transaction!
|
173
173
|
|
174
174
|
def change
|
@@ -186,7 +186,7 @@ If you forget `disable_ddl_transaction!`, the migration will fail. Also, note th
|
|
186
186
|
Rails adds a non-concurrent index to references by default, which is problematic for Postgres.
|
187
187
|
|
188
188
|
```ruby
|
189
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
189
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.0]
|
190
190
|
def change
|
191
191
|
add_reference :users, :city
|
192
192
|
end
|
@@ -198,12 +198,11 @@ end
|
|
198
198
|
Make sure the index is added concurrently.
|
199
199
|
|
200
200
|
```ruby
|
201
|
-
class AddReferenceToUsers < ActiveRecord::Migration[
|
201
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.0]
|
202
202
|
disable_ddl_transaction!
|
203
203
|
|
204
204
|
def change
|
205
|
-
add_reference :users, :city, index:
|
206
|
-
add_index :users, :city_id, algorithm: :concurrently
|
205
|
+
add_reference :users, :city, index: {algorithm: :concurrently}
|
207
206
|
end
|
208
207
|
end
|
209
208
|
```
|
@@ -217,7 +216,7 @@ For polymorphic references, add a compound index on type and id.
|
|
217
216
|
In Postgres, new foreign keys are validated by default, which acquires an `AccessExclusiveLock` that can be [expensive on large tables](https://travisofthenorth.com/blog/2017/2/2/postgres-adding-foreign-keys-with-zero-downtime).
|
218
217
|
|
219
218
|
```ruby
|
220
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
219
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
221
220
|
def change
|
222
221
|
add_foreign_key :users, :orders
|
223
222
|
end
|
@@ -231,7 +230,7 @@ Instead, validate it in a separate migration with a more agreeable `RowShareLock
|
|
231
230
|
For Rails 5.2+, use:
|
232
231
|
|
233
232
|
```ruby
|
234
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[
|
233
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
235
234
|
def change
|
236
235
|
add_foreign_key :users, :orders, validate: false
|
237
236
|
end
|
@@ -241,7 +240,7 @@ end
|
|
241
240
|
Then validate it in a separate migration.
|
242
241
|
|
243
242
|
```ruby
|
244
|
-
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[
|
243
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
245
244
|
def change
|
246
245
|
validate_foreign_key :users, :orders
|
247
246
|
end
|
@@ -277,7 +276,7 @@ end
|
|
277
276
|
#### Bad
|
278
277
|
|
279
278
|
```ruby
|
280
|
-
class RenameSomeColumn < ActiveRecord::Migration[
|
279
|
+
class RenameSomeColumn < ActiveRecord::Migration[6.0]
|
281
280
|
def change
|
282
281
|
rename_column :users, :some_column, :new_name
|
283
282
|
end
|
@@ -287,7 +286,7 @@ end
|
|
287
286
|
or
|
288
287
|
|
289
288
|
```ruby
|
290
|
-
class ChangeSomeColumnType < ActiveRecord::Migration[
|
289
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[6.0]
|
291
290
|
def change
|
292
291
|
change_column :users, :some_column, :new_type
|
293
292
|
end
|
@@ -312,7 +311,7 @@ A safer approach is to:
|
|
312
311
|
#### Bad
|
313
312
|
|
314
313
|
```ruby
|
315
|
-
class RenameUsersToCustomers < ActiveRecord::Migration[
|
314
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[6.0]
|
316
315
|
def change
|
317
316
|
rename_table :users, :customers
|
318
317
|
end
|
@@ -337,7 +336,7 @@ A safer approach is to:
|
|
337
336
|
The `force` option can drop an existing table.
|
338
337
|
|
339
338
|
```ruby
|
340
|
-
class CreateUsers < ActiveRecord::Migration[
|
339
|
+
class CreateUsers < ActiveRecord::Migration[6.0]
|
341
340
|
def change
|
342
341
|
create_table :users, force: true do |t|
|
343
342
|
# ...
|
@@ -348,10 +347,10 @@ end
|
|
348
347
|
|
349
348
|
#### Good
|
350
349
|
|
351
|
-
|
350
|
+
Create tables without the `force` option.
|
352
351
|
|
353
352
|
```ruby
|
354
|
-
class CreateUsers < ActiveRecord::Migration[
|
353
|
+
class CreateUsers < ActiveRecord::Migration[6.0]
|
355
354
|
def change
|
356
355
|
create_table :users do |t|
|
357
356
|
# ...
|
@@ -367,7 +366,7 @@ end
|
|
367
366
|
This generates a single `UPDATE` statement to set the default value.
|
368
367
|
|
369
368
|
```ruby
|
370
|
-
class ChangeSomeColumnNull < ActiveRecord::Migration[
|
369
|
+
class ChangeSomeColumnNull < ActiveRecord::Migration[6.0]
|
371
370
|
def change
|
372
371
|
change_column_null :users, :some_column, false, "default_value"
|
373
372
|
end
|
@@ -379,7 +378,7 @@ end
|
|
379
378
|
Backfill the column [safely](#backfilling-data). Then use:
|
380
379
|
|
381
380
|
```ruby
|
382
|
-
class ChangeSomeColumnNull < ActiveRecord::Migration[
|
381
|
+
class ChangeSomeColumnNull < ActiveRecord::Migration[6.0]
|
383
382
|
def change
|
384
383
|
change_column_null :users, :some_column, false
|
385
384
|
end
|
@@ -393,7 +392,7 @@ end
|
|
393
392
|
In Postgres, there’s no equality operator for the `json` column type, which causes issues for `SELECT DISTINCT` queries.
|
394
393
|
|
395
394
|
```ruby
|
396
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
395
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
|
397
396
|
def change
|
398
397
|
add_column :users, :properties, :json
|
399
398
|
end
|
@@ -405,7 +404,7 @@ end
|
|
405
404
|
Use `jsonb` instead.
|
406
405
|
|
407
406
|
```ruby
|
408
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[
|
407
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
|
409
408
|
def change
|
410
409
|
add_column :users, :properties, :jsonb
|
411
410
|
end
|
@@ -421,7 +420,7 @@ end
|
|
421
420
|
Adding a non-unique index with more than three columns rarely improves performance.
|
422
421
|
|
423
422
|
```ruby
|
424
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
423
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
|
425
424
|
def change
|
426
425
|
add_index :users, [:a, :b, :c, :d]
|
427
426
|
end
|
@@ -433,7 +432,7 @@ end
|
|
433
432
|
Instead, start an index with columns that narrow down the results the most.
|
434
433
|
|
435
434
|
```ruby
|
436
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
435
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
|
437
436
|
def change
|
438
437
|
add_index :users, [:b, :d]
|
439
438
|
end
|
@@ -447,7 +446,7 @@ end
|
|
447
446
|
To mark a step in the migration as safe, despite using a method that might otherwise be dangerous, wrap it in a `safety_assured` block.
|
448
447
|
|
449
448
|
```ruby
|
450
|
-
class MySafeMigration < ActiveRecord::Migration[
|
449
|
+
class MySafeMigration < ActiveRecord::Migration[6.0]
|
451
450
|
def change
|
452
451
|
safety_assured { remove_column :users, :some_column }
|
453
452
|
end
|
data/lib/strong_migrations.rb
CHANGED
@@ -116,12 +116,18 @@ end"
|
|
116
116
|
options ||= {}
|
117
117
|
|
118
118
|
index_value = options.fetch(:index, true)
|
119
|
-
|
119
|
+
concurrently_set = index_value.is_a?(Hash) && index_value[:algorithm] == :concurrently
|
120
|
+
|
121
|
+
if postgresql? && index_value && !concurrently_set
|
120
122
|
columns = options[:polymorphic] ? [:"#{reference}_type", :"#{reference}_id"] : :"#{reference}_id"
|
121
123
|
|
122
|
-
|
123
|
-
|
124
|
-
|
124
|
+
if index_value.is_a?(Hash)
|
125
|
+
options[:index] = options[:index].merge(algorithm: :concurrently)
|
126
|
+
else
|
127
|
+
options = options.merge(index: {algorithm: :concurrently})
|
128
|
+
end
|
129
|
+
|
130
|
+
raise_error :add_reference, command: command_str(method, [table, reference, options])
|
125
131
|
end
|
126
132
|
when :execute
|
127
133
|
raise_error :execute, header: "Possibly dangerous operation"
|
@@ -236,7 +242,14 @@ end"
|
|
236
242
|
last_arg = args[-1]
|
237
243
|
if last_arg.is_a?(Hash)
|
238
244
|
if last_arg.any?
|
239
|
-
str_args << last_arg.map
|
245
|
+
str_args << last_arg.map do |k, v|
|
246
|
+
if v.is_a?(Hash)
|
247
|
+
# pretty index: {algorithm: :concurrently}
|
248
|
+
"#{k}: {#{v.map { |k2, v2| "#{k2}: #{v2.inspect}" }.join(", ")}}"
|
249
|
+
else
|
250
|
+
"#{k}: #{v.inspect}"
|
251
|
+
end
|
252
|
+
end.join(", ")
|
240
253
|
end
|
241
254
|
else
|
242
255
|
str_args << last_arg.inspect
|
@@ -1,23 +1,18 @@
|
|
1
1
|
module StrongMigrations
|
2
2
|
module Migration
|
3
|
-
def initialize(*args)
|
4
|
-
super
|
5
|
-
@checker = StrongMigrations::Checker.new(self)
|
6
|
-
end
|
7
|
-
|
8
3
|
def migrate(direction)
|
9
|
-
|
4
|
+
strong_migrations_checker.direction = direction
|
10
5
|
super
|
11
6
|
end
|
12
7
|
|
13
8
|
def method_missing(method, *args)
|
14
|
-
|
9
|
+
strong_migrations_checker.perform(method, *args) do
|
15
10
|
super
|
16
11
|
end
|
17
12
|
end
|
18
13
|
|
19
14
|
def safety_assured
|
20
|
-
|
15
|
+
strong_migrations_checker.safety_assured do
|
21
16
|
yield
|
22
17
|
end
|
23
18
|
end
|
@@ -25,5 +20,11 @@ module StrongMigrations
|
|
25
20
|
def stop!(message, header: "Custom check")
|
26
21
|
raise StrongMigrations::UnsafeMigration, "\n=== #{header} #strong_migrations ===\n\n#{message}\n"
|
27
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def strong_migrations_checker
|
27
|
+
@strong_migrations_checker ||= StrongMigrations::Checker.new(self)
|
28
|
+
end
|
28
29
|
end
|
29
30
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-
|
13
|
+
date: 2019-10-28 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -122,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: '0'
|
124
124
|
requirements: []
|
125
|
-
rubygems_version: 3.0.
|
125
|
+
rubygems_version: 3.0.3
|
126
126
|
signing_key:
|
127
127
|
specification_version: 4
|
128
128
|
summary: Catch unsafe migrations in development
|