strong_migrations 0.7.4 → 0.7.8

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: 2bd2b26a8cb5d3577ffaad3878ff961dec2bc6cb55c51897b2681febbf8f0b10
4
- data.tar.gz: b48c9ff1050345351a9f938c6dd6dc8530fbc4842e0fc3c60702287fa3fb825c
3
+ metadata.gz: 38cd87929d4889113bc2cc13aa786965c8b86fadf05d1d0439e2c927becfef61
4
+ data.tar.gz: 0f17d511058c0bd1852adcddf9a2e978b8e407002ddf201be7aedb6c797dd294
5
5
  SHA512:
6
- metadata.gz: 4bc2817b7878c3041442e4e4607db55b2861e7cba16220e635ee28a4b9ebd6dbd0a7ab16033555fa51f4f98601fe0eda198fa91e3030dfff63839c7cdc9aa028
7
- data.tar.gz: 254e72cd2ce295759819f05cde920cf4c3ea9ae0d45af9f21d6ac11d55549d0d3544f97f511dc5b1dc0cf44c03c25af4208e4ab4f317449d9aabea90cd485b99
6
+ metadata.gz: d28ef8e2d9cd837fac0a5e72743e1b88e7ef268ce9660e5119c27b50cc0e5215ae20e36a8be570a987e967f4a8bdf47ccd68eb6ee66f7188b787d3d64596c514
7
+ data.tar.gz: f1e012eed6af3ebcc0ffbf5a1f4b90438ee054597f41a9420a3645af8535fff4cb3ef64b69abdc547fbf20a14658b03f0492395ad46c6bfc79b26726424c81c4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.7.8 (2021-08-03)
2
+
3
+ - Fixed issue with `add_reference ..., foreign_key: {to_table: ...}` with `safe_by_default`
4
+
5
+ ## 0.7.7 (2021-06-07)
6
+
7
+ - Removed timeouts and `auto_analyze` from schema load
8
+
9
+ ## 0.7.6 (2021-01-17)
10
+
11
+ - Fixed `NOT NULL` constraint check for quoted columns
12
+ - Fixed deprecation warning with Active Record 6.1
13
+
14
+ ## 0.7.5 (2021-01-12)
15
+
16
+ - Added checks for `add_check_constraint` and `validate_check_constraint`
17
+
1
18
  ## 0.7.4 (2020-12-16)
2
19
 
3
20
  - Added `safe_by_default` option to install generator
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Bob Remeika and David Waller, 2015-2020 Andrew Kane
1
+ Copyright (c) 2013 Bob Remeika and David Waller, 2015-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -43,7 +43,7 @@ end
43
43
 
44
44
  Deploy the code, then wrap this step in a safety_assured { ... } block.
45
45
 
46
- class RemoveColumn < ActiveRecord::Migration[6.0]
46
+ class RemoveColumn < ActiveRecord::Migration[6.1]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -66,6 +66,7 @@ Potentially dangerous operations:
66
66
  - [renaming a column](#renaming-a-column)
67
67
  - [renaming a table](#renaming-a-table)
68
68
  - [creating a table with the force option](#creating-a-table-with-the-force-option)
69
+ - [adding a check constraint](#adding-a-check-constraint)
69
70
  - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
70
71
  - [executing SQL directly](#executing-SQL-directly)
71
72
 
@@ -89,7 +90,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
89
90
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
90
91
 
91
92
  ```ruby
92
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
93
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
93
94
  def change
94
95
  remove_column :users, :some_column
95
96
  end
@@ -110,7 +111,7 @@ end
110
111
  3. Write a migration to remove the column (wrap in `safety_assured` block)
111
112
 
112
113
  ```ruby
113
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
114
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
114
115
  def change
115
116
  safety_assured { remove_column :users, :some_column }
116
117
  end
@@ -126,7 +127,7 @@ end
126
127
  In earlier versions of Postgres, MySQL, and MariaDB, adding a column with a default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
127
128
 
128
129
  ```ruby
129
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
130
+ class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
130
131
  def change
131
132
  add_column :users, :some_column, :text, default: "default_value"
132
133
  end
@@ -140,7 +141,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t
140
141
  Instead, add the column without a default value, then change the default.
141
142
 
142
143
  ```ruby
143
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
144
+ class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
144
145
  def up
145
146
  add_column :users, :some_column, :text
146
147
  change_column_default :users, :some_column, "default_value"
@@ -161,7 +162,7 @@ See the next section for how to backfill.
161
162
  Active Record creates a transaction around each migration, and backfilling in the same transaction that alters a table keeps the table locked 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/).
162
163
 
163
164
  ```ruby
164
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
165
+ class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
165
166
  def change
166
167
  add_column :users, :some_column, :text
167
168
  User.update_all some_column: "default_value"
@@ -176,7 +177,7 @@ Also, running a single query to update data can cause issues for large tables.
176
177
  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!`.
177
178
 
178
179
  ```ruby
179
- class BackfillSomeColumn < ActiveRecord::Migration[6.0]
180
+ class BackfillSomeColumn < ActiveRecord::Migration[6.1]
180
181
  disable_ddl_transaction!
181
182
 
182
183
  def up
@@ -195,7 +196,7 @@ end
195
196
  Changing the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
196
197
 
197
198
  ```ruby
198
- class ChangeSomeColumnType < ActiveRecord::Migration[6.0]
199
+ class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
199
200
  def change
200
201
  change_column :users, :some_column, :new_type
201
202
  end
@@ -234,7 +235,7 @@ A safer approach is to:
234
235
  Renaming a column that’s in use will cause errors in your application.
235
236
 
236
237
  ```ruby
237
- class RenameSomeColumn < ActiveRecord::Migration[6.0]
238
+ class RenameSomeColumn < ActiveRecord::Migration[6.1]
238
239
  def change
239
240
  rename_column :users, :some_column, :new_name
240
241
  end
@@ -259,7 +260,7 @@ A safer approach is to:
259
260
  Renaming a table that’s in use will cause errors in your application.
260
261
 
261
262
  ```ruby
262
- class RenameUsersToCustomers < ActiveRecord::Migration[6.0]
263
+ class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
263
264
  def change
264
265
  rename_table :users, :customers
265
266
  end
@@ -284,7 +285,7 @@ A safer approach is to:
284
285
  The `force` option can drop an existing table.
285
286
 
286
287
  ```ruby
287
- class CreateUsers < ActiveRecord::Migration[6.0]
288
+ class CreateUsers < ActiveRecord::Migration[6.1]
288
289
  def change
289
290
  create_table :users, force: true do |t|
290
291
  # ...
@@ -298,7 +299,7 @@ end
298
299
  Create tables without the `force` option.
299
300
 
300
301
  ```ruby
301
- class CreateUsers < ActiveRecord::Migration[6.0]
302
+ class CreateUsers < ActiveRecord::Migration[6.1]
302
303
  def change
303
304
  create_table :users do |t|
304
305
  # ...
@@ -309,6 +310,48 @@ end
309
310
 
310
311
  If you intend to drop an existing table, run `drop_table` first.
311
312
 
313
+ ### Adding a check constraint
314
+
315
+ :turtle: Safe by default available
316
+
317
+ #### Bad
318
+
319
+ Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
320
+
321
+ ```ruby
322
+ class AddCheckConstraint < ActiveRecord::Migration[6.1]
323
+ def change
324
+ add_check_constraint :users, "price > 0", name: "price_check"
325
+ end
326
+ end
327
+ ```
328
+
329
+ #### Good - Postgres
330
+
331
+ Add the check constraint without validating existing rows:
332
+
333
+ ```ruby
334
+ class AddCheckConstraint < ActiveRecord::Migration[6.1]
335
+ def change
336
+ add_check_constraint :users, "price > 0", name: "price_check", validate: false
337
+ end
338
+ end
339
+ ```
340
+
341
+ Then validate them in a separate migration.
342
+
343
+ ```ruby
344
+ class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
345
+ def change
346
+ validate_check_constraint :users, name: "price_check"
347
+ end
348
+ end
349
+ ```
350
+
351
+ #### Good - MySQL and MariaDB
352
+
353
+ [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (check constraints can be added with `NOT ENFORCED`, but enforcing blocks writes).
354
+
312
355
  ### Setting NOT NULL on an existing column
313
356
 
314
357
  :turtle: Safe by default available
@@ -318,7 +361,7 @@ If you intend to drop an existing table, run `drop_table` first.
318
361
  Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
319
362
 
320
363
  ```ruby
321
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
364
+ class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
322
365
  def change
323
366
  change_column_null :users, :some_column, false
324
367
  end
@@ -327,7 +370,19 @@ end
327
370
 
328
371
  #### Good - Postgres
329
372
 
330
- Instead, add a check constraint:
373
+ Instead, add a check constraint.
374
+
375
+ For Rails 6.1, use:
376
+
377
+ ```ruby
378
+ class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
379
+ def change
380
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
381
+ end
382
+ end
383
+ ```
384
+
385
+ For Rails < 6.1, use:
331
386
 
332
387
  ```ruby
333
388
  class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
@@ -339,7 +394,23 @@ class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
339
394
  end
340
395
  ```
341
396
 
342
- Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column, but it won’t show up in `schema.rb`. In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
397
+ Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column (but it won’t show up in `schema.rb` in Rails < 6.1). In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
398
+
399
+ For Rails 6.1, use:
400
+
401
+ ```ruby
402
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1]
403
+ def change
404
+ validate_check_constraint :users, name: "users_some_column_null"
405
+
406
+ # in Postgres 12+, you can then safely set NOT NULL on the column
407
+ change_column_null :users, :some_column, false
408
+ remove_check_constraint :users, name: "users_some_column_null"
409
+ end
410
+ end
411
+ ```
412
+
413
+ For Rails < 6.1, use:
343
414
 
344
415
  ```ruby
345
416
  class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
@@ -366,7 +437,7 @@ end
366
437
  Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
367
438
 
368
439
  ```ruby
369
- class ExecuteSQL < ActiveRecord::Migration[6.0]
440
+ class ExecuteSQL < ActiveRecord::Migration[6.1]
370
441
  def change
371
442
  safety_assured { execute "..." }
372
443
  end
@@ -382,7 +453,7 @@ end
382
453
  In Postgres, adding an index non-concurrently blocks writes.
383
454
 
384
455
  ```ruby
385
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
456
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
386
457
  def change
387
458
  add_index :users, :some_column
388
459
  end
@@ -394,7 +465,7 @@ end
394
465
  Add indexes concurrently.
395
466
 
396
467
  ```ruby
397
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
468
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
398
469
  disable_ddl_transaction!
399
470
 
400
471
  def change
@@ -420,7 +491,7 @@ rails g index table column
420
491
  Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
421
492
 
422
493
  ```ruby
423
- class AddReferenceToUsers < ActiveRecord::Migration[6.0]
494
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
424
495
  def change
425
496
  add_reference :users, :city
426
497
  end
@@ -432,7 +503,7 @@ end
432
503
  Make sure the index is added concurrently.
433
504
 
434
505
  ```ruby
435
- class AddReferenceToUsers < ActiveRecord::Migration[6.0]
506
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
436
507
  disable_ddl_transaction!
437
508
 
438
509
  def change
@@ -450,7 +521,7 @@ end
450
521
  In Postgres, adding a foreign key blocks writes on both tables.
451
522
 
452
523
  ```ruby
453
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
524
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
454
525
  def change
455
526
  add_foreign_key :users, :orders
456
527
  end
@@ -460,7 +531,7 @@ end
460
531
  or
461
532
 
462
533
  ```ruby
463
- class AddReferenceToUsers < ActiveRecord::Migration[6.0]
534
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
464
535
  def change
465
536
  add_reference :users, :order, foreign_key: true
466
537
  end
@@ -474,7 +545,7 @@ Add the foreign key without validating existing rows, then validate them in a se
474
545
  For Rails 5.2+, use:
475
546
 
476
547
  ```ruby
477
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
548
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
478
549
  def change
479
550
  add_foreign_key :users, :orders, validate: false
480
551
  end
@@ -484,7 +555,7 @@ end
484
555
  Then:
485
556
 
486
557
  ```ruby
487
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.0]
558
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
488
559
  def change
489
560
  validate_foreign_key :users, :orders
490
561
  end
@@ -522,7 +593,7 @@ end
522
593
  In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
523
594
 
524
595
  ```ruby
525
- class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
596
+ class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
526
597
  def change
527
598
  add_column :users, :properties, :json
528
599
  end
@@ -534,7 +605,7 @@ end
534
605
  Use `jsonb` instead.
535
606
 
536
607
  ```ruby
537
- class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
608
+ class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
538
609
  def change
539
610
  add_column :users, :properties, :jsonb
540
611
  end
@@ -548,7 +619,7 @@ end
548
619
  Adding a non-unique index with more than three columns rarely improves performance.
549
620
 
550
621
  ```ruby
551
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
622
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
552
623
  def change
553
624
  add_index :users, [:a, :b, :c, :d]
554
625
  end
@@ -560,7 +631,7 @@ end
560
631
  Instead, start an index with columns that narrow down the results the most.
561
632
 
562
633
  ```ruby
563
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
634
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
564
635
  def change
565
636
  add_index :users, [:b, :d]
566
637
  end
@@ -574,7 +645,7 @@ For Postgres, be sure to add them concurrently.
574
645
  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.
575
646
 
576
647
  ```ruby
577
- class MySafeMigration < ActiveRecord::Migration[6.0]
648
+ class MySafeMigration < ActiveRecord::Migration[6.1]
578
649
  def change
579
650
  safety_assured { remove_column :users, :some_column }
580
651
  end
@@ -585,12 +656,11 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
585
656
 
586
657
  ## Safe by Default
587
658
 
588
- *Experimental*
589
-
590
659
  Make operations safe by default.
591
660
 
592
661
  - adding and removing an index
593
662
  - adding a foreign key
663
+ - adding a check constraint
594
664
  - setting NOT NULL on an existing column
595
665
 
596
666
  Add to `config/initializers/strong_migrations.rb`:
@@ -748,11 +818,15 @@ StrongMigrations.auto_analyze = true
748
818
 
749
819
  ## Faster Migrations
750
820
 
751
- Only dump the schema when adding a new migration. If you use Git, create an initializer with:
821
+ Only dump the schema when adding a new migration. If you use Git, add to the end of your `Rakefile`:
752
822
 
753
- ```ruby
754
- ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
755
- `git status db/migrate/ --porcelain`.present?
823
+ ```rb
824
+ task :faster_migrations do
825
+ ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
826
+ `git status db/migrate/ --porcelain`.present?
827
+ end
828
+
829
+ task "db:migrate": "faster_migrations"
756
830
  ```
757
831
 
758
832
  ## Schema Sanity
@@ -31,7 +31,11 @@ module StrongMigrations
31
31
  end
32
32
 
33
33
  def adapter
34
- ActiveRecord::Base.connection_config[:adapter].to_s
34
+ if ActiveRecord::VERSION::STRING.to_f >= 6.1
35
+ ActiveRecord::Base.connection_db_config.adapter.to_s
36
+ else
37
+ ActiveRecord::Base.connection_config[:adapter].to_s
38
+ end
35
39
  end
36
40
 
37
41
  def postgresql?
@@ -216,6 +216,30 @@ end",
216
216
 
217
217
  validate_foreign_key:
218
218
  "Validating a foreign key while writes are blocked is dangerous.
219
+ Use disable_ddl_transaction! or a separate migration.",
220
+
221
+ add_check_constraint:
222
+ "Adding a check constraint key blocks reads and writes while every row is checked.
223
+ Instead, add the check constraint without validating existing rows,
224
+ then validate them in a separate migration.
225
+
226
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
227
+ def change
228
+ %{add_check_constraint_code}
229
+ end
230
+ end
231
+
232
+ class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
233
+ def change
234
+ %{validate_check_constraint_code}
235
+ end
236
+ end",
237
+
238
+ add_check_constraint_mysql:
239
+ "Adding a check constraint to an existing table is not safe with your database engine.",
240
+
241
+ validate_check_constraint:
242
+ "Validating a check constraint while writes are blocked is dangerous.
219
243
  Use disable_ddl_transaction! or a separate migration."
220
244
  }
221
245
  self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
@@ -219,8 +219,7 @@ Then add the foreign key in separate migrations."
219
219
  if postgresql?
220
220
  safe = false
221
221
  if postgresql_version >= Gem::Version.new("12")
222
- # TODO likely need to quote the column in some situations
223
- safe = constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" }
222
+ safe = constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" || c["def"] == "CHECK ((#{connection.quote_column_name(column)} IS NOT NULL))" }
224
223
  end
225
224
 
226
225
  unless safe
@@ -231,18 +230,38 @@ Then add the foreign key in separate migrations."
231
230
  validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
232
231
  remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
233
232
 
234
- validate_constraint_code = String.new(safety_assured_str(validate_code))
233
+ validate_constraint_code =
234
+ if ar_version >= 6.1
235
+ String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
236
+ else
237
+ String.new(safety_assured_str(validate_code))
238
+ end
239
+
235
240
  if postgresql_version >= Gem::Version.new("12")
236
241
  change_args = [table, column, null]
237
242
 
238
243
  validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
239
- validate_constraint_code << "\n #{safety_assured_str(remove_code)}"
244
+
245
+ if ar_version >= 6.1
246
+ validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
247
+ else
248
+ validate_constraint_code << "\n #{safety_assured_str(remove_code)}"
249
+ end
240
250
  end
241
251
 
242
252
  return safe_change_column_null(add_code, validate_code, change_args, remove_code) if StrongMigrations.safe_by_default
243
253
 
254
+ add_constraint_code =
255
+ if ar_version >= 6.1
256
+ # only quote when needed
257
+ expr_column = column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
258
+ command_str(:add_check_constraint, [table, "#{expr_column} IS NOT NULL", {name: constraint_name, validate: false}])
259
+ else
260
+ safety_assured_str(add_code)
261
+ end
262
+
244
263
  raise_error :change_column_null_postgresql,
245
- add_constraint_code: safety_assured_str(add_code),
264
+ add_constraint_code: add_constraint_code,
246
265
  validate_constraint_code: validate_constraint_code
247
266
  end
248
267
  elsif mysql? || mariadb?
@@ -257,10 +276,10 @@ Then add the foreign key in separate migrations."
257
276
  options ||= {}
258
277
 
259
278
  # always validated before 5.2
260
- validate = options.fetch(:validate, true) || ActiveRecord::VERSION::STRING < "5.2"
279
+ validate = options.fetch(:validate, true) || ar_version < 5.2
261
280
 
262
281
  if postgresql? && validate
263
- if ActiveRecord::VERSION::STRING < "5.2"
282
+ if ar_version < 5.2
264
283
  # fk name logic from rails
265
284
  primary_key = options[:primary_key] || "id"
266
285
  column = options[:column] || "#{to_table.to_s.singularize}_id"
@@ -287,6 +306,29 @@ Then add the foreign key in separate migrations."
287
306
  if postgresql? && writes_blocked?
288
307
  raise_error :validate_foreign_key
289
308
  end
309
+ when :add_check_constraint
310
+ table, expression, options = args
311
+ options ||= {}
312
+
313
+ if !new_table?(table)
314
+ if postgresql? && options[:validate] != false
315
+ add_options = options.merge(validate: false)
316
+ name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
317
+ validate_options = {name: name}
318
+
319
+ return safe_add_check_constraint(table, expression, add_options, validate_options) if StrongMigrations.safe_by_default
320
+
321
+ raise_error :add_check_constraint,
322
+ add_check_constraint_code: command_str("add_check_constraint", [table, expression, add_options]),
323
+ validate_check_constraint_code: command_str("validate_check_constraint", [table, validate_options])
324
+ elsif mysql? || mariadb?
325
+ raise_error :add_check_constraint_mysql
326
+ end
327
+ end
328
+ when :validate_check_constraint
329
+ if postgresql? && writes_blocked?
330
+ raise_error :validate_check_constraint
331
+ end
290
332
  end
291
333
 
292
334
  StrongMigrations.checks.each do |check|
@@ -298,11 +340,7 @@ Then add the foreign key in separate migrations."
298
340
 
299
341
  # outdated statistics + a new index can hurt performance of existing queries
300
342
  if StrongMigrations.auto_analyze && direction == :up && method == :add_index
301
- if postgresql?
302
- connection.execute "ANALYZE #{connection.quote_table_name(args[0].to_s)}"
303
- elsif mariadb? || mysql?
304
- connection.execute "ANALYZE TABLE #{connection.quote_table_name(args[0].to_s)}"
305
- end
343
+ analyze_table(args[0])
306
344
  end
307
345
 
308
346
  result
@@ -354,8 +392,7 @@ Then add the foreign key in separate migrations."
354
392
  end
355
393
 
356
394
  def safe?
357
- @safe || ENV["SAFETY_ASSURED"] || @migration.is_a?(ActiveRecord::Schema) ||
358
- (direction == :down && !StrongMigrations.check_down) || version_safe?
395
+ @safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
359
396
  end
360
397
 
361
398
  def version_safe?
@@ -410,6 +447,10 @@ Then add the foreign key in separate migrations."
410
447
  Gem::Version.new(version)
411
448
  end
412
449
 
450
+ def ar_version
451
+ ActiveRecord::VERSION::STRING.to_f
452
+ end
453
+
413
454
  def check_lock_timeout
414
455
  limit = StrongMigrations.lock_timeout_limit
415
456
 
@@ -462,6 +503,14 @@ Then add the foreign key in separate migrations."
462
503
  end
463
504
  end
464
505
 
506
+ def analyze_table(table)
507
+ if postgresql?
508
+ connection.execute "ANALYZE #{connection.quote_table_name(table.to_s)}"
509
+ elsif mariadb? || mysql?
510
+ connection.execute "ANALYZE TABLE #{connection.quote_table_name(table.to_s)}"
511
+ end
512
+ end
513
+
465
514
  def constraints(table_name)
466
515
  query = <<~SQL
467
516
  SELECT
@@ -7,6 +7,8 @@ module StrongMigrations
7
7
  end
8
8
 
9
9
  def method_missing(method, *args)
10
+ return super if is_a?(ActiveRecord::Schema)
11
+
10
12
  strong_migrations_checker.perform(method, *args) do
11
13
  super
12
14
  end
@@ -1,7 +1,7 @@
1
1
  module StrongMigrations
2
2
  module SafeMethods
3
3
  def safe_by_default_method?(method)
4
- StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :change_column_null].include?(method)
4
+ StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
5
5
  end
6
6
 
7
7
  # TODO check if invalid index with expected name exists and remove if needed
@@ -30,7 +30,11 @@ module StrongMigrations
30
30
  (ActiveRecord::Base.pluralize_table_names ? reference.to_s.pluralize : reference).to_sym
31
31
  end
32
32
 
33
- @migration.add_foreign_key(table, name)
33
+ if reference
34
+ @migration.add_foreign_key(table, name, column: "#{reference}_id")
35
+ else
36
+ @migration.add_foreign_key(table, name)
37
+ end
34
38
  end
35
39
  end
36
40
  dir.down do
@@ -67,6 +71,19 @@ module StrongMigrations
67
71
  end
68
72
  end
69
73
 
74
+ def safe_add_check_constraint(table, expression, add_options, validate_options)
75
+ @migration.reversible do |dir|
76
+ dir.up do
77
+ @migration.add_check_constraint(table, expression, **add_options)
78
+ disable_transaction
79
+ @migration.validate_check_constraint(table, **validate_options)
80
+ end
81
+ dir.down do
82
+ @migration.remove_check_constraint(table, expression, **add_options)
83
+ end
84
+ end
85
+ end
86
+
70
87
  def safe_change_column_null(add_code, validate_code, change_args, remove_code)
71
88
  @migration.reversible do |dir|
72
89
  dir.up do
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "0.7.4"
2
+ VERSION = "0.7.8"
3
3
  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.7.4
4
+ version: 0.7.8
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: 2020-12-16 00:00:00.000000000 Z
13
+ date: 2021-08-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0'
140
140
  requirements: []
141
- rubygems_version: 3.2.0.rc.1
141
+ rubygems_version: 3.2.22
142
142
  signing_key:
143
143
  specification_version: 4
144
144
  summary: Catch unsafe migrations in development