strong_migrations 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|