strong_migrations 0.6.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -2,61 +2,103 @@
2
2
 
3
3
  Catch unsafe migrations in development
4
4
 
5
- Supports for PostgreSQL, MySQL, and MariaDB
5
+ &nbsp;&nbsp;✓&nbsp;&nbsp;Detects potentially dangerous operations<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Prevents them from running by default<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Provides instructions on safer ways to do what you want
6
+
7
+ Supports PostgreSQL, MySQL, and MariaDB
6
8
 
7
9
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
8
10
 
9
- [![Build Status](https://travis-ci.org/ankane/strong_migrations.svg?branch=master)](https://travis-ci.org/ankane/strong_migrations)
11
+ [![Build Status](https://github.com/ankane/strong_migrations/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/strong_migrations/actions)
10
12
 
11
13
  ## Installation
12
14
 
13
15
  Add this line to your application’s Gemfile:
14
16
 
15
17
  ```ruby
16
- gem 'strong_migrations'
18
+ gem "strong_migrations"
17
19
  ```
18
20
 
21
+ And run:
22
+
23
+ ```sh
24
+ bundle install
25
+ rails generate strong_migrations:install
26
+ ```
27
+
28
+ Strong Migrations sets a long statement timeout for migrations so you can set a [short statement timeout](#app-timeouts) for your application.
29
+
19
30
  ## How It Works
20
31
 
21
- Strong Migrations detects potentially dangerous operations in migrations, prevents them from running by default, and provides instructions on safer ways to do what you want.
32
+ When you run a migration that’s potentially dangerous, you’ll see an error message like:
22
33
 
23
- ![Screenshot](https://ankane.org/images/strong-migrations.png)
34
+ ```txt
35
+ === Dangerous operation detected #strong_migrations ===
24
36
 
25
- ## Dangerous Operations
37
+ Active Record caches attributes, which causes problems
38
+ when removing columns. Be sure to ignore the column:
26
39
 
27
- The following operations can cause downtime or errors:
40
+ class User < ApplicationRecord
41
+ self.ignored_columns += ["name"]
42
+ end
43
+
44
+ Deploy the code, then wrap this step in a safety_assured { ... } block.
28
45
 
29
- - [[+]](#removing-a-column) removing a column
30
- - [[+]](#adding-a-column-with-a-default-value) adding a column with a default value
31
- - [[+]](#backfilling-data) backfilling data
32
- - [[+]](#adding-an-index) adding an index non-concurrently
33
- - [[+]](#adding-a-reference) adding a reference
34
- - [[+]](#adding-a-foreign-key) adding a foreign key
35
- - [[+]](#renaming-or-changing-the-type-of-a-column) changing the type of a column
36
- - [[+]](#renaming-or-changing-the-type-of-a-column) renaming a column
37
- - [[+]](#renaming-a-table) renaming a table
38
- - [[+]](#creating-a-table-with-the-force-option) creating a table with the `force` option
39
- - [[+]](#setting-not-null-on-an-existing-column) setting `NOT NULL` on an existing column
40
- - [[+]](#adding-a-json-column) adding a `json` column
46
+ class RemoveColumn < ActiveRecord::Migration[8.0]
47
+ def change
48
+ safety_assured { remove_column :users, :name }
49
+ end
50
+ end
51
+ ```
41
52
 
42
- Optional checks:
53
+ An operation is classified as dangerous if it either:
43
54
 
44
- - [[+]](#removing-an-index) removing an index non-concurrently
55
+ - Blocks reads or writes for more than a few seconds (after a lock is acquired)
56
+ - Has a good chance of causing application errors
57
+
58
+ ## Checks
59
+
60
+ Potentially dangerous operations:
61
+
62
+ - [removing a column](#removing-a-column)
63
+ - [changing the type of a column](#changing-the-type-of-a-column)
64
+ - [renaming a column](#renaming-a-column)
65
+ - [renaming a table](#renaming-a-table)
66
+ - [creating a table with the force option](#creating-a-table-with-the-force-option)
67
+ - [adding an auto-incrementing column](#adding-an-auto-incrementing-column)
68
+ - [adding a stored generated column](#adding-a-stored-generated-column)
69
+ - [adding a check constraint](#adding-a-check-constraint)
70
+ - [executing SQL directly](#executing-SQL-directly)
71
+ - [backfilling data](#backfilling-data)
72
+
73
+ Postgres-specific checks:
74
+
75
+ - [adding an index non-concurrently](#adding-an-index-non-concurrently)
76
+ - [adding a reference](#adding-a-reference)
77
+ - [adding a foreign key](#adding-a-foreign-key)
78
+ - [adding a unique constraint](#adding-a-unique-constraint)
79
+ - [adding an exclusion constraint](#adding-an-exclusion-constraint)
80
+ - [adding a json column](#adding-a-json-column)
81
+ - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
82
+ - [adding a column with a volatile default value](#adding-a-column-with-a-volatile-default-value)
83
+
84
+ Config-specific checks:
85
+
86
+ - [changing the default value of a column](#changing-the-default-value-of-a-column)
45
87
 
46
88
  Best practices:
47
89
 
48
- - [[+]](#keeping-non-unique-indexes-to-three-columns-or-less) keeping non-unique indexes to three columns or less
90
+ - [keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)
49
91
 
50
- ## The Zero Downtime Way
92
+ You can also add [custom checks](#custom-checks) or [disable specific checks](#disable-checks).
51
93
 
52
94
  ### Removing a column
53
95
 
54
96
  #### Bad
55
97
 
56
- ActiveRecord caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
98
+ Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
57
99
 
58
100
  ```ruby
59
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
101
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
60
102
  def change
61
103
  remove_column :users, :some_column
62
104
  end
@@ -65,388 +107,461 @@ end
65
107
 
66
108
  #### Good
67
109
 
68
- 1. Tell ActiveRecord to ignore the column from its cache
110
+ 1. Tell Active Record to ignore the column from its cache
69
111
 
70
112
  ```ruby
71
113
  class User < ApplicationRecord
72
- self.ignored_columns = ["some_column"]
114
+ self.ignored_columns += ["some_column"]
73
115
  end
74
116
  ```
75
117
 
76
- 2. Deploy code
118
+ 2. Deploy the code
77
119
  3. Write a migration to remove the column (wrap in `safety_assured` block)
78
120
 
79
121
  ```ruby
80
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
122
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
81
123
  def change
82
124
  safety_assured { remove_column :users, :some_column }
83
125
  end
84
126
  end
85
127
  ```
86
128
 
87
- 4. Deploy and run migration
88
-
89
- ### Adding a column with a default value
129
+ 4. Deploy and run the migration
130
+ 5. Remove the line added in step 1
90
131
 
91
- Note: This operation is safe in Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+
132
+ ### Changing the type of a column
92
133
 
93
134
  #### Bad
94
135
 
95
- Adding a column with a default value to an existing table causes the entire table to be rewritten.
136
+ 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.
96
137
 
97
138
  ```ruby
98
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
139
+ class ChangeSomeColumnType < ActiveRecord::Migration[8.0]
99
140
  def change
100
- add_column :users, :some_column, :text, default: "default_value"
141
+ change_column :users, :some_column, :new_type
101
142
  end
102
143
  end
103
144
  ```
104
145
 
105
- #### Good
146
+ Some changes don’t require a table rewrite and are safe in Postgres:
106
147
 
107
- Instead, add the column without a default value, then change the default.
148
+ Type | Safe Changes
149
+ --- | ---
150
+ `cidr` | Changing to `inet`
151
+ `citext` | Changing to `text` if not indexed, changing to `string` with no `:limit` if not indexed
152
+ `datetime` | Increasing or removing `:precision`, changing to `timestamptz` when session time zone is UTC in Postgres 12+
153
+ `decimal` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
154
+ `interval` | Increasing or removing `:precision`
155
+ `numeric` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
156
+ `string` | Increasing or removing `:limit`, changing to `text`, changing `citext` if not indexed
157
+ `text` | Changing to `string` with no `:limit`, changing to `citext` if not indexed
158
+ `time` | Increasing or removing `:precision`
159
+ `timestamptz` | Increasing or removing `:limit`, changing to `datetime` when session time zone is UTC in Postgres 12+
108
160
 
109
- ```ruby
110
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
111
- def up
112
- add_column :users, :some_column, :text
113
- change_column_default :users, :some_column, "default_value"
114
- end
161
+ And some in MySQL and MariaDB:
115
162
 
116
- def down
117
- remove_column :users, :some_column
118
- end
119
- end
120
- ```
163
+ Type | Safe Changes
164
+ --- | ---
165
+ `string` | Increasing `:limit` from under 63 up to 63, increasing `:limit` from over 63 to the max (the threshold can be different if using an encoding other than `utf8mb4` - for instance, it’s 85 for `utf8mb3` and 255 for `latin1`)
166
+
167
+ #### Good
121
168
 
122
- See the next section for how to backfill.
169
+ A safer approach is to:
123
170
 
124
- ### Backfilling data
171
+ 1. Create a new column
172
+ 2. Write to both columns
173
+ 3. Backfill data from the old column to the new column
174
+ 4. Move reads from the old column to the new column
175
+ 5. Stop writing to the old column
176
+ 6. Drop the old column
177
+
178
+ ### Renaming a column
125
179
 
126
180
  #### Bad
127
181
 
128
- 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/).
182
+ Renaming a column that’s in use will cause errors in your application.
129
183
 
130
184
  ```ruby
131
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
185
+ class RenameSomeColumn < ActiveRecord::Migration[8.0]
132
186
  def change
133
- add_column :users, :some_column, :text
134
- User.update_all some_column: "default_value"
187
+ rename_column :users, :some_column, :new_name
135
188
  end
136
189
  end
137
190
  ```
138
191
 
139
- Also, running a single query to update data can cause issues for large tables.
140
-
141
192
  #### Good
142
193
 
143
- 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!`.
194
+ A safer approach is to:
144
195
 
145
- ```ruby
146
- class BackfillSomeColumn < ActiveRecord::Migration[6.0]
147
- disable_ddl_transaction!
196
+ 1. Create a new column
197
+ 2. Write to both columns
198
+ 3. Backfill data from the old column to the new column
199
+ 4. Move reads from the old column to the new column
200
+ 5. Stop writing to the old column
201
+ 6. Drop the old column
148
202
 
149
- def up
150
- User.unscoped.in_batches do |relation|
151
- relation.update_all some_column: "default_value"
152
- sleep(0.01) # throttle
153
- end
203
+ ### Renaming a table
204
+
205
+ #### Bad
206
+
207
+ Renaming a table that’s in use will cause errors in your application.
208
+
209
+ ```ruby
210
+ class RenameUsersToCustomers < ActiveRecord::Migration[8.0]
211
+ def change
212
+ rename_table :users, :customers
154
213
  end
155
214
  end
156
215
  ```
157
216
 
158
- ### Adding an index
217
+ #### Good
218
+
219
+ A safer approach is to:
220
+
221
+ 1. Create a new table
222
+ 2. Write to both tables
223
+ 3. Backfill data from the old table to the new table
224
+ 4. Move reads from the old table to the new table
225
+ 5. Stop writing to the old table
226
+ 6. Drop the old table
227
+
228
+ ### Creating a table with the force option
159
229
 
160
230
  #### Bad
161
231
 
162
- In Postgres, adding an index non-concurrently locks the table.
232
+ The `force` option can drop an existing table.
163
233
 
164
234
  ```ruby
165
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
235
+ class CreateUsers < ActiveRecord::Migration[8.0]
166
236
  def change
167
- add_index :users, :some_column
237
+ create_table :users, force: true do |t|
238
+ # ...
239
+ end
168
240
  end
169
241
  end
170
242
  ```
171
243
 
172
244
  #### Good
173
245
 
174
- Add indexes concurrently.
246
+ Create tables without the `force` option.
175
247
 
176
248
  ```ruby
177
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
178
- disable_ddl_transaction!
179
-
249
+ class CreateUsers < ActiveRecord::Migration[8.0]
180
250
  def change
181
- add_index :users, :some_column, algorithm: :concurrently
251
+ create_table :users do |t|
252
+ # ...
253
+ end
182
254
  end
183
255
  end
184
256
  ```
185
257
 
186
- If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
258
+ If you intend to drop an existing table, run `drop_table` first.
187
259
 
188
- With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
189
-
190
- ```sh
191
- rails g index table column
192
- ```
193
-
194
- ### Adding a reference
260
+ ### Adding an auto-incrementing column
195
261
 
196
262
  #### Bad
197
263
 
198
- Rails adds an index non-concurrently to references by default, which is problematic for Postgres.
264
+ Adding an auto-incrementing column (`serial`/`bigserial` in Postgres and `AUTO_INCREMENT` in MySQL and MariaDB) 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.
199
265
 
200
266
  ```ruby
201
- class AddReferenceToUsers < ActiveRecord::Migration[6.0]
267
+ class AddIdToCitiesUsers < ActiveRecord::Migration[8.0]
202
268
  def change
203
- add_reference :users, :city
269
+ add_column :cities_users, :id, :primary_key
204
270
  end
205
271
  end
206
272
  ```
207
273
 
208
- #### Good
209
-
210
- Make sure the index is added concurrently.
274
+ With MySQL and MariaDB, this can also [generate different values on replicas](https://dev.mysql.com/doc/mysql-replication-excerpt/8.0/en/replication-features-auto-increment.html) if using statement-based replication.
211
275
 
212
- ```ruby
213
- class AddReferenceToUsers < ActiveRecord::Migration[6.0]
214
- disable_ddl_transaction!
276
+ #### Good
215
277
 
216
- def change
217
- add_reference :users, :city, index: {algorithm: :concurrently}
218
- end
219
- end
220
- ```
278
+ Create a new table and migrate the data with the same steps as [renaming a table](#renaming-a-table).
221
279
 
222
- ### Adding a foreign key
280
+ ### Adding a stored generated column
223
281
 
224
282
  #### Bad
225
283
 
226
- In Postgres, new foreign keys are validated by default, which acquires a `ShareRowExclusiveLock` that can be [expensive on large tables](https://travisofthenorth.com/blog/2017/2/2/postgres-adding-foreign-keys-with-zero-downtime).
284
+ Adding a stored generated 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.
227
285
 
228
286
  ```ruby
229
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
287
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
230
288
  def change
231
- add_foreign_key :users, :orders
289
+ add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
232
290
  end
233
291
  end
234
292
  ```
235
293
 
236
294
  #### Good
237
295
 
238
- Instead, validate it in a separate migration with a more agreeable `RowShareLock`. This approach is documented by Postgres to have “[the least impact on other work](https://www.postgresql.org/docs/current/sql-altertable.html).”
296
+ Add a non-generated column and use callbacks or triggers instead (or a virtual generated column with MySQL and MariaDB).
297
+
298
+ ### Adding a check constraint
299
+
300
+ :turtle: Safe by default available
239
301
 
240
- For Rails 5.2+, use:
302
+ #### Bad
303
+
304
+ Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
241
305
 
242
306
  ```ruby
243
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
307
+ class AddCheckConstraint < ActiveRecord::Migration[8.0]
244
308
  def change
245
- add_foreign_key :users, :orders, validate: false
309
+ add_check_constraint :users, "price > 0", name: "price_check"
246
310
  end
247
311
  end
248
312
  ```
249
313
 
250
- Then validate it in a separate migration.
314
+ #### Good - Postgres
315
+
316
+ Add the check constraint without validating existing rows:
251
317
 
252
318
  ```ruby
253
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.0]
319
+ class AddCheckConstraint < ActiveRecord::Migration[8.0]
254
320
  def change
255
- validate_foreign_key :users, :orders
321
+ add_check_constraint :users, "price > 0", name: "price_check", validate: false
256
322
  end
257
323
  end
258
324
  ```
259
325
 
260
- For Rails < 5.2, use:
326
+ Then validate them in a separate migration.
261
327
 
262
328
  ```ruby
263
- class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
329
+ class ValidateCheckConstraint < ActiveRecord::Migration[8.0]
264
330
  def change
265
- safety_assured do
266
- execute 'ALTER TABLE "users" ADD CONSTRAINT "fk_rails_c1e9b98e31" FOREIGN KEY ("order_id") REFERENCES "orders" ("id") NOT VALID'
267
- end
331
+ validate_check_constraint :users, name: "price_check"
268
332
  end
269
333
  end
270
334
  ```
271
335
 
272
- Then validate it in a separate migration.
336
+ #### Good - MySQL and MariaDB
337
+
338
+ [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).
339
+
340
+ ### Executing SQL directly
341
+
342
+ Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
273
343
 
274
344
  ```ruby
275
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
345
+ class ExecuteSQL < ActiveRecord::Migration[8.0]
276
346
  def change
277
- safety_assured do
278
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
279
- end
347
+ safety_assured { execute "..." }
280
348
  end
281
349
  end
282
350
  ```
283
351
 
284
- ### Renaming or changing the type of a column
352
+ ### Backfilling data
353
+
354
+ Note: Strong Migrations does not detect dangerous backfills.
285
355
 
286
356
  #### Bad
287
357
 
358
+ 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/).
359
+
288
360
  ```ruby
289
- class RenameSomeColumn < ActiveRecord::Migration[6.0]
361
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
290
362
  def change
291
- rename_column :users, :some_column, :new_name
363
+ add_column :users, :some_column, :text
364
+ User.update_all some_column: "default_value"
292
365
  end
293
366
  end
294
367
  ```
295
368
 
296
- or
369
+ Also, running a single query to update data can cause issues for large tables.
370
+
371
+ #### Good
372
+
373
+ 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!`.
297
374
 
298
375
  ```ruby
299
- class ChangeSomeColumnType < ActiveRecord::Migration[6.0]
300
- def change
301
- change_column :users, :some_column, :new_type
376
+ class BackfillSomeColumn < ActiveRecord::Migration[8.0]
377
+ disable_ddl_transaction!
378
+
379
+ def up
380
+ User.unscoped.in_batches(of: 10000) do |relation|
381
+ relation.where(some_column: nil).update_all some_column: "default_value"
382
+ sleep(0.01) # throttle
383
+ end
302
384
  end
303
385
  end
304
386
  ```
305
387
 
306
- One exception is changing a `varchar` column to `text`, which is safe in Postgres.
307
-
308
- #### Good
309
-
310
- A safer approach is to:
388
+ Note: If backfilling with a method other than `update_all`, use `User.reset_column_information` to ensure the model has up-to-date column information.
311
389
 
312
- 1. Create a new column
313
- 2. Write to both columns
314
- 3. Backfill data from the old column to the new column
315
- 4. Move reads from the old column to the new column
316
- 5. Stop writing to the old column
317
- 6. Drop the old column
390
+ ### Adding an index non-concurrently
318
391
 
319
- ### Renaming a table
392
+ :turtle: Safe by default available
320
393
 
321
394
  #### Bad
322
395
 
396
+ In Postgres, adding an index non-concurrently blocks writes.
397
+
323
398
  ```ruby
324
- class RenameUsersToCustomers < ActiveRecord::Migration[6.0]
399
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
325
400
  def change
326
- rename_table :users, :customers
401
+ add_index :users, :some_column
327
402
  end
328
403
  end
329
404
  ```
330
405
 
331
406
  #### Good
332
407
 
333
- A safer approach is to:
408
+ Add indexes concurrently.
334
409
 
335
- 1. Create a new table
336
- 2. Write to both tables
337
- 3. Backfill data from the old table to new table
338
- 4. Move reads from the old table to the new table
339
- 5. Stop writing to the old table
340
- 6. Drop the old table
410
+ ```ruby
411
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
412
+ disable_ddl_transaction!
341
413
 
342
- ### Creating a table with the force option
414
+ def change
415
+ add_index :users, :some_column, algorithm: :concurrently
416
+ end
417
+ end
418
+ ```
419
+
420
+ If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
421
+
422
+ With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
423
+
424
+ ```sh
425
+ rails g index table column
426
+ ```
427
+
428
+ ### Adding a reference
429
+
430
+ :turtle: Safe by default available
343
431
 
344
432
  #### Bad
345
433
 
346
- The `force` option can drop an existing table.
434
+ Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
347
435
 
348
436
  ```ruby
349
- class CreateUsers < ActiveRecord::Migration[6.0]
437
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
350
438
  def change
351
- create_table :users, force: true do |t|
352
- # ...
353
- end
439
+ add_reference :users, :city
354
440
  end
355
441
  end
356
442
  ```
357
443
 
358
444
  #### Good
359
445
 
360
- Create tables without the `force` option.
446
+ Make sure the index is added concurrently.
361
447
 
362
448
  ```ruby
363
- class CreateUsers < ActiveRecord::Migration[6.0]
449
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
450
+ disable_ddl_transaction!
451
+
364
452
  def change
365
- create_table :users do |t|
366
- # ...
367
- end
453
+ add_reference :users, :city, index: {algorithm: :concurrently}
368
454
  end
369
455
  end
370
456
  ```
371
457
 
372
- ### Setting `NOT NULL` on an existing column
458
+ ### Adding a foreign key
459
+
460
+ :turtle: Safe by default available
373
461
 
374
462
  #### Bad
375
463
 
376
- In Postgres, setting `NOT NULL` on an existing column requires an `AccessExclusiveLock`, which is expensive on large tables.
464
+ In Postgres, adding a foreign key blocks writes on both tables.
377
465
 
378
466
  ```ruby
379
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
467
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
380
468
  def change
381
- change_column_null :users, :some_column, false
469
+ add_foreign_key :users, :orders
470
+ end
471
+ end
472
+ ```
473
+
474
+ or
475
+
476
+ ```ruby
477
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
478
+ def change
479
+ add_reference :users, :order, foreign_key: true
382
480
  end
383
481
  end
384
482
  ```
385
483
 
386
484
  #### Good
387
485
 
388
- Instead, add a constraint:
486
+ Add the foreign key without validating existing rows:
389
487
 
390
488
  ```ruby
391
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
489
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
392
490
  def change
393
- safety_assured do
394
- execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
395
- end
491
+ add_foreign_key :users, :orders, validate: false
396
492
  end
397
493
  end
398
494
  ```
399
495
 
400
- Then validate it in a separate migration.
496
+ Then validate them in a separate migration.
401
497
 
402
498
  ```ruby
403
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
499
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.0]
404
500
  def change
405
- safety_assured do
406
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
407
- end
501
+ validate_foreign_key :users, :orders
408
502
  end
409
503
  end
410
504
  ```
411
505
 
412
- Note: This is not 100% the same as `NOT NULL` column constraint. Here’s a [good explanation](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c).
413
-
414
- ### Using change_column_null with a default value
506
+ ### Adding a unique constraint
415
507
 
416
508
  #### Bad
417
509
 
418
- This generates a single `UPDATE` statement to set the default value.
510
+ In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
419
511
 
420
512
  ```ruby
421
- class ChangeSomeColumnNull < ActiveRecord::Migration[6.0]
513
+ class AddUniqueConstraint < ActiveRecord::Migration[8.0]
422
514
  def change
423
- change_column_null :users, :some_column, false, "default_value"
515
+ add_unique_constraint :users, :some_column
424
516
  end
425
517
  end
426
518
  ```
427
519
 
428
520
  #### Good
429
521
 
430
- Backfill the column [safely](#backfilling-data). Then use:
522
+ Create a unique index concurrently, then use it for the constraint.
431
523
 
432
524
  ```ruby
433
- class ChangeSomeColumnNull < ActiveRecord::Migration[6.0]
525
+ class AddUniqueConstraint < ActiveRecord::Migration[8.0]
526
+ disable_ddl_transaction!
527
+
528
+ def up
529
+ add_index :users, :some_column, unique: true, algorithm: :concurrently
530
+ add_unique_constraint :users, using_index: "index_users_on_some_column"
531
+ end
532
+
533
+ def down
534
+ remove_unique_constraint :users, :some_column
535
+ end
536
+ end
537
+ ```
538
+
539
+ ### Adding an exclusion constraint
540
+
541
+ #### Bad
542
+
543
+ In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
544
+
545
+ ```ruby
546
+ class AddExclusionConstraint < ActiveRecord::Migration[8.0]
434
547
  def change
435
- change_column_null :users, :some_column, false
548
+ add_exclusion_constraint :users, "number WITH =", using: :gist
436
549
  end
437
550
  end
438
551
  ```
439
552
 
440
- Note: In Postgres, `change_column_null` is still [not safe](#setting-not-null-on-an-existing-column) with this method.
553
+ #### Good
554
+
555
+ [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (exclusion constraints cannot be marked `NOT VALID`).
441
556
 
442
557
  ### Adding a json column
443
558
 
444
559
  #### Bad
445
560
 
446
- In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries.
561
+ In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
447
562
 
448
563
  ```ruby
449
- class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
564
+ class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
450
565
  def change
451
566
  add_column :users, :properties, :json
452
567
  end
@@ -458,56 +573,120 @@ end
458
573
  Use `jsonb` instead.
459
574
 
460
575
  ```ruby
461
- class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
576
+ class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
462
577
  def change
463
578
  add_column :users, :properties, :jsonb
464
579
  end
465
580
  end
466
581
  ```
467
582
 
468
- ## Optional Checks
583
+ ### Setting NOT NULL on an existing column
584
+
585
+ :turtle: Safe by default available
469
586
 
470
- Some operations rarely cause issues in practice, but can be checked if desired. Enable checks with:
587
+ #### Bad
588
+
589
+ In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
471
590
 
472
591
  ```ruby
473
- StrongMigrations.enable_check(:remove_index)
592
+ class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
593
+ def change
594
+ change_column_null :users, :some_column, false
595
+ end
596
+ end
597
+ ```
598
+
599
+ #### Good
600
+
601
+ Instead, add a check constraint.
602
+
603
+ ```ruby
604
+ class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
605
+ def change
606
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
607
+ end
608
+ end
474
609
  ```
475
610
 
476
- To start a check only after a specific migration, use:
611
+ Then validate it in a separate migration. Once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
477
612
 
478
613
  ```ruby
479
- StrongMigrations.enable_check(:remove_index, start_after: 20170101000000)
614
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.0]
615
+ def up
616
+ validate_check_constraint :users, name: "users_some_column_null"
617
+ change_column_null :users, :some_column, false
618
+ remove_check_constraint :users, name: "users_some_column_null"
619
+ end
620
+
621
+ def down
622
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
623
+ change_column_null :users, :some_column, true
624
+ end
625
+ end
480
626
  ```
481
627
 
482
- ### Removing an index
628
+ ### Adding a column with a volatile default value
483
629
 
484
630
  #### Bad
485
631
 
486
- In Postgres, removing an index non-concurrently locks the table for a brief period.
632
+ Adding a column with a volatile default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked.
487
633
 
488
634
  ```ruby
489
- class RemoveSomeIndexFromUsers < ActiveRecord::Migration[6.0]
635
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
490
636
  def change
491
- remove_index :users, :some_column
637
+ add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
492
638
  end
493
639
  end
494
640
  ```
495
641
 
496
642
  #### Good
497
643
 
498
- Remove indexes concurrently.
644
+ Instead, add the column without a default value, then change the default.
499
645
 
500
646
  ```ruby
501
- class RemoveSomeIndexFromUsers < ActiveRecord::Migration[6.0]
502
- disable_ddl_transaction!
647
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
648
+ def up
649
+ add_column :users, :some_column, :uuid
650
+ change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
651
+ end
652
+
653
+ def down
654
+ remove_column :users, :some_column
655
+ end
656
+ end
657
+ ```
658
+
659
+ Then [backfill the data](#backfilling-data).
660
+
661
+ ### Changing the default value of a column
662
+
663
+ #### Bad
664
+
665
+ Rails < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
503
666
 
667
+ ```ruby
668
+ class ChangeSomeColumnDefault < ActiveRecord::Migration[6.1]
504
669
  def change
505
- remove_index :users, column: :some_column, algorithm: :concurrently
670
+ change_column_default :users, :some_column, from: "old", to: "new"
506
671
  end
507
672
  end
673
+
674
+ User.create!(some_column: "old") # can insert "new"
675
+ ```
676
+
677
+ #### Good
678
+
679
+ Disable partial writes in `config/application.rb`. For Rails < 7, use:
680
+
681
+ ```ruby
682
+ config.active_record.partial_writes = false
508
683
  ```
509
684
 
510
- ## Best Practices
685
+ For Rails 7+, use:
686
+
687
+ ```ruby
688
+ config.active_record.partial_inserts = false
689
+ ```
511
690
 
512
691
  ### Keeping non-unique indexes to three columns or less
513
692
 
@@ -516,7 +695,7 @@ end
516
695
  Adding a non-unique index with more than three columns rarely improves performance.
517
696
 
518
697
  ```ruby
519
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
698
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
520
699
  def change
521
700
  add_index :users, [:a, :b, :c, :d]
522
701
  end
@@ -528,9 +707,9 @@ end
528
707
  Instead, start an index with columns that narrow down the results the most.
529
708
 
530
709
  ```ruby
531
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
710
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
532
711
  def change
533
- add_index :users, [:b, :d]
712
+ add_index :users, [:d, :b]
534
713
  end
535
714
  end
536
715
  ```
@@ -542,7 +721,7 @@ For Postgres, be sure to add them concurrently.
542
721
  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.
543
722
 
544
723
  ```ruby
545
- class MySafeMigration < ActiveRecord::Migration[6.0]
724
+ class MySafeMigration < ActiveRecord::Migration[8.0]
546
725
  def change
547
726
  safety_assured { remove_column :users, :some_column }
548
727
  end
@@ -551,6 +730,21 @@ end
551
730
 
552
731
  Certain methods like `execute` and `change_table` cannot be inspected and are prevented from running by default. Make sure what you’re doing is really safe and use this pattern.
553
732
 
733
+ ## Safe by Default
734
+
735
+ Make certain operations safe by default. This allows you to write the code under the "Bad" section, but the migration will be performed as if you had written the "Good" version.
736
+
737
+ - adding and removing an index
738
+ - adding a foreign key
739
+ - adding a check constraint
740
+ - setting NOT NULL on an existing column
741
+
742
+ Add to `config/initializers/strong_migrations.rb`:
743
+
744
+ ```ruby
745
+ StrongMigrations.safe_by_default = true
746
+ ```
747
+
554
748
  ## Custom Checks
555
749
 
556
750
  Add your own custom checks with:
@@ -567,6 +761,16 @@ Use the `stop!` method to stop migrations.
567
761
 
568
762
  Note: Since `remove_column` always requires a `safety_assured` block, it’s not possible to add a custom check for `remove_column` operations.
569
763
 
764
+ ## Opt-in Checks
765
+
766
+ ### Removing an index non-concurrently
767
+
768
+ Postgres supports removing indexes concurrently, but removing them non-concurrently shouldn’t be an issue for most applications. You can enable this check with:
769
+
770
+ ```ruby
771
+ StrongMigrations.enable_check(:remove_index)
772
+ ```
773
+
570
774
  ## Disable Checks
571
775
 
572
776
  Disable specific checks with:
@@ -575,106 +779,206 @@ Disable specific checks with:
575
779
  StrongMigrations.disable_check(:add_index)
576
780
  ```
577
781
 
578
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
782
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
579
783
 
580
- ## Existing Migrations
784
+ ## Skip Databases
581
785
 
582
- To mark migrations as safe that were created before installing this gem, create an initializer with:
786
+ Skip checks and other functionality for specific databases with:
583
787
 
584
788
  ```ruby
585
- StrongMigrations.start_after = 20170101000000
789
+ StrongMigrations.skip_database(:catalog)
586
790
  ```
587
791
 
588
- Use the version from your latest migration.
792
+ Note: This does not affect `alphabetize_schema`.
589
793
 
590
- ## Dangerous Tasks
794
+ ## Down Migrations / Rollbacks
591
795
 
592
- For safety, dangerous database tasks are disabled in production - `db:drop`, `db:reset`, `db:schema:load`, and `db:structure:load`. To get around this, use:
796
+ By default, checks are disabled when migrating down. Enable them with:
593
797
 
594
- ```sh
595
- SAFETY_ASSURED=1 rails db:drop
798
+ ```ruby
799
+ StrongMigrations.check_down = true
596
800
  ```
597
801
 
598
- ## Faster Migrations
802
+ ## Custom Messages
599
803
 
600
- Only dump the schema when adding a new migration. If you use Git, create an initializer with:
804
+ To customize specific messages, create an initializer with:
601
805
 
602
806
  ```ruby
603
- ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
604
- `git status db/migrate/ --porcelain`.present?
807
+ StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
605
808
  ```
606
809
 
607
- ## Schema Sanity
810
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
811
+
812
+ ## Migration Timeouts
813
+
814
+ It’s extremely important to set a short lock timeout for migrations. This way, if a migration can’t acquire a lock in a timely manner, other statements won’t be stuck behind it. We also recommend setting a long statement timeout so migrations can run for a while.
608
815
 
609
- Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to the end of your `Rakefile`:
816
+ Create `config/initializers/strong_migrations.rb` with:
610
817
 
611
818
  ```ruby
612
- task "db:schema:dump": "strong_migrations:alphabetize_columns"
819
+ StrongMigrations.lock_timeout = 10.seconds
820
+ StrongMigrations.statement_timeout = 1.hour
613
821
  ```
614
822
 
615
- ## Custom Messages
823
+ Or set the timeouts directly on the database user that runs migrations. For Postgres, use:
616
824
 
617
- To customize specific messages, create an initializer with:
825
+ ```sql
826
+ ALTER ROLE myuser SET lock_timeout = '10s';
827
+ ALTER ROLE myuser SET statement_timeout = '1h';
828
+ ```
829
+
830
+ Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
831
+
832
+ ## App Timeouts
833
+
834
+ We recommend adding timeouts to `config/database.yml` to prevent connections from hanging and individual queries from taking up too many resources in controllers, jobs, the Rails console, and other places.
835
+
836
+ For Postgres:
837
+
838
+ ```yml
839
+ production:
840
+ connect_timeout: 5
841
+ variables:
842
+ statement_timeout: 15s
843
+ lock_timeout: 10s
844
+ ```
845
+
846
+ Note: If you use PgBouncer in transaction mode, you must set the statement and lock timeouts on the database user as shown above.
847
+
848
+ For MySQL:
849
+
850
+ ```yml
851
+ production:
852
+ connect_timeout: 5
853
+ read_timeout: 5
854
+ write_timeout: 5
855
+ variables:
856
+ max_execution_time: 15000 # ms
857
+ lock_wait_timeout: 10 # sec
858
+
859
+ ```
860
+
861
+ For MariaDB:
862
+
863
+ ```yml
864
+ production:
865
+ connect_timeout: 5
866
+ read_timeout: 5
867
+ write_timeout: 5
868
+ variables:
869
+ max_statement_time: 15 # sec
870
+ lock_wait_timeout: 10 # sec
871
+ ```
872
+
873
+ For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
874
+
875
+ ## Invalid Indexes
876
+
877
+ In Postgres, adding an index non-concurrently can leave behind an invalid index if the lock timeout is reached. Running the migration again can result in an error.
878
+
879
+ To automatically remove the invalid index when the migration runs again, use:
618
880
 
619
881
  ```ruby
620
- StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
882
+ StrongMigrations.remove_invalid_indexes = true
621
883
  ```
622
884
 
623
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
885
+ Note: This feature is experimental.
624
886
 
625
- ## Analyze Tables
887
+ ## Lock Timeout Retries
626
888
 
627
- Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
889
+ Note: This feature is experimental.
890
+
891
+ There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
892
+
893
+ - If a lock timeout happens outside a transaction, the statement is retried
894
+ - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
895
+
896
+ Add to `config/initializers/strong_migrations.rb`:
628
897
 
629
898
  ```ruby
630
- StrongMigrations.auto_analyze = true
899
+ StrongMigrations.lock_timeout_retries = 3
900
+ ```
901
+
902
+ Set the delay between retries with:
903
+
904
+ ```ruby
905
+ StrongMigrations.lock_timeout_retry_delay = 10.seconds
631
906
  ```
632
907
 
908
+ ## Existing Migrations
909
+
910
+ To mark migrations as safe that were created before installing this gem, create an initializer with:
911
+
912
+ ```ruby
913
+ StrongMigrations.start_after = 20250101000000
914
+ ```
915
+
916
+ Use the version from your latest migration.
917
+
633
918
  ## Target Version
634
919
 
635
- If your development database version is different from production, you can specify the production version so the right checks are run in development.
920
+ If your development database version is different from production, you can specify the production version so the right checks run in development.
636
921
 
637
922
  ```ruby
638
- StrongMigrations.target_postgresql_version = "10"
639
- StrongMigrations.target_mysql_version = "8.0.12"
640
- StrongMigrations.target_mariadb_version = "10.3.2"
923
+ StrongMigrations.target_version = 10 # or 8.0, 10.5, etc
641
924
  ```
642
925
 
926
+ The major version works well for Postgres, while the major and minor version is recommended for MySQL and MariaDB.
927
+
643
928
  For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
644
929
 
645
- ## Timeouts
930
+ If your app has multiple databases with different versions, you can use:
931
+
932
+ ```ruby
933
+ StrongMigrations.target_version = {primary: 13, catalog: 15}
934
+ ```
646
935
 
647
- It’s a good idea to set a long statement timeout and a short lock timeout for migrations. This way, migrations can run for a while, and if a migration can’t acquire a lock in a timely manner, other statements won’t be stuck behind it.
936
+ ## Analyze Tables
648
937
 
649
- You can use:
938
+ Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
650
939
 
651
940
  ```ruby
652
- StrongMigrations.statement_timeout = 1.hour
653
- StrongMigrations.lock_timeout = 10.seconds
941
+ StrongMigrations.auto_analyze = true
654
942
  ```
655
943
 
656
- Or set the timeouts directly on the database user that runs migrations. For Postgres, use:
944
+ ## Faster Migrations
657
945
 
658
- ```sql
659
- ALTER ROLE myuser SET statement_timeout = '1h';
660
- ALTER ROLE myuser SET lock_timeout = '10s';
946
+ Only dump the schema when adding a new migration. If you use Git, add to `config/environments/development.rb`:
947
+
948
+ ```rb
949
+ config.active_record.dump_schema_after_migration = `git status db/migrate/ --porcelain`.present?
661
950
  ```
662
951
 
663
- Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
952
+ ## Schema Sanity
953
+
954
+ Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to `config/initializers/strong_migrations.rb`:
955
+
956
+ ```ruby
957
+ StrongMigrations.alphabetize_schema = true
958
+ ```
664
959
 
665
960
  ## Permissions
666
961
 
667
962
  We recommend using a [separate database user](https://ankane.org/postgres-users) for migrations when possible so you don’t need to grant your app user permission to alter tables.
668
963
 
964
+ ## Smaller Projects
965
+
966
+ You probably don’t need this gem for smaller projects, as operations that are unsafe at scale can be perfectly safe on smaller, low-traffic tables.
967
+
669
968
  ## Additional Reading
670
969
 
671
- - [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
672
970
  - [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
971
+ - [MySQL InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/en/innodb-online-ddl-operations.html)
972
+ - [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
673
973
 
674
974
  ## Credits
675
975
 
676
976
  Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations) and [Sean Huber](https://github.com/LendingHome/zero_downtime_migrations) for the bad/good readme format.
677
977
 
978
+ ## History
979
+
980
+ View the [changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md)
981
+
678
982
  ## Contributing
679
983
 
680
984
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
@@ -690,5 +994,12 @@ To get started with development:
690
994
  git clone https://github.com/ankane/strong_migrations.git
691
995
  cd strong_migrations
692
996
  bundle install
997
+
998
+ # Postgres
999
+ createdb strong_migrations_test
693
1000
  bundle exec rake test
1001
+
1002
+ # MySQL and MariaDB
1003
+ mysqladmin create strong_migrations_test
1004
+ ADAPTER=mysql2 bundle exec rake test
694
1005
  ```