strong_migrations 0.3.1 → 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -16
- data/LICENSE.txt +1 -1
- data/README.md +656 -111
- data/lib/generators/strong_migrations/install_generator.rb +46 -0
- data/lib/generators/strong_migrations/templates/initializer.rb.tt +25 -0
- data/lib/strong_migrations.rb +150 -33
- data/lib/strong_migrations/checker.rb +599 -0
- data/lib/strong_migrations/database_tasks.rb +1 -1
- data/lib/strong_migrations/migration.rb +15 -176
- data/lib/strong_migrations/railtie.rb +0 -4
- data/lib/strong_migrations/safe_methods.rb +125 -0
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/tasks/strong_migrations.rake +0 -6
- metadata +33 -17
- data/lib/strong_migrations/unsafe_migration.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f16481df30c6b961477b4a537034097aa1d41a262bd7f2741c40b7e6dae9cb9e
|
4
|
+
data.tar.gz: 816bc9a4f9810bce6ac6d5e0a52b185800515794ca5e3d27b5f4d512a9190115
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ac9f7c38958c1cc2e80efa3f895730087ac92a3675be31c308f9f8df827ced3323ca8bc3e69c99e2a79b92ef0a5a8b595a76715bf55c45b5698fcdd43b5a586
|
7
|
+
data.tar.gz: 2cee8d3013ae8780f0bcbce33f22a219375c29f059335902cdfe2285ec557307229b7ec4544e6be59ea1ee7cd039f2b23e9b3693efc41f11f677cbbed1495896
|
data/CHANGELOG.md
CHANGED
@@ -1,78 +1,186 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.7.6 (2021-01-17)
|
2
|
+
|
3
|
+
- Fixed `NOT NULL` constraint check for quoted columns
|
4
|
+
- Fixed deprecation warning with Active Record 6.1
|
5
|
+
|
6
|
+
## 0.7.5 (2021-01-12)
|
7
|
+
|
8
|
+
- Added checks for `add_check_constraint` and `validate_check_constraint`
|
9
|
+
|
10
|
+
## 0.7.4 (2020-12-16)
|
11
|
+
|
12
|
+
- Added `safe_by_default` option to install generator
|
13
|
+
- Fixed warnings with Active Record 6.1
|
14
|
+
|
15
|
+
## 0.7.3 (2020-11-24)
|
16
|
+
|
17
|
+
- Added `safe_by_default` option
|
18
|
+
|
19
|
+
## 0.7.2 (2020-10-25)
|
20
|
+
|
21
|
+
- Added support for float timeouts
|
22
|
+
|
23
|
+
## 0.7.1 (2020-07-27)
|
24
|
+
|
25
|
+
- Added `target_version` option to replace database-specific options
|
26
|
+
|
27
|
+
## 0.7.0 (2020-07-22)
|
28
|
+
|
29
|
+
- Added `check_down` option
|
30
|
+
- Added check for `change_column` with `null: false`
|
31
|
+
- Added check for `validate_foreign_key`
|
32
|
+
- Improved error messages
|
33
|
+
- Made auto analyze less verbose in Postgres
|
34
|
+
- Decreasing the length limit of a `varchar` column or adding a limit is not safe in Postgres
|
35
|
+
- Removed safety checks for `db` rake tasks (Rails 5+ handles this)
|
36
|
+
|
37
|
+
## 0.6.8 (2020-05-13)
|
38
|
+
|
39
|
+
- `change_column_null` on a column with a `NOT NULL` constraint is safe in Postgres 12+
|
40
|
+
|
41
|
+
## 0.6.7 (2020-05-13)
|
42
|
+
|
43
|
+
- Improved comments in initializer
|
44
|
+
- Fixed string timeouts for Postgres
|
45
|
+
|
46
|
+
## 0.6.6 (2020-05-08)
|
47
|
+
|
48
|
+
- Added warnings for missing and long lock timeouts
|
49
|
+
- Added install generator
|
50
|
+
|
51
|
+
## 0.6.5 (2020-05-06)
|
52
|
+
|
53
|
+
- Fixed deprecation warnings with Ruby 2.7
|
54
|
+
|
55
|
+
## 0.6.4 (2020-04-16)
|
56
|
+
|
57
|
+
- Added check for `add_reference` with `foreign_key: true`
|
58
|
+
|
59
|
+
## 0.6.3 (2020-04-04)
|
60
|
+
|
61
|
+
- Increasing precision of `decimal` or `numeric` column is safe in Postgres 9.2+
|
62
|
+
- Making `decimal` or `numeric` column unconstrained is safe in Postgres 9.2+
|
63
|
+
- Changing between `timestamp` and `timestamptz` when session time zone is UTC in Postgres 12+
|
64
|
+
- Increasing the length of a `varchar` column from under 255 up to 255 in MySQL and MariaDB
|
65
|
+
- Increasing the length of a `varchar` column over 255 in MySQL and MariaDB
|
66
|
+
|
67
|
+
## 0.6.2 (2020-02-03)
|
68
|
+
|
69
|
+
- Fixed PostgreSQL version check
|
70
|
+
|
71
|
+
## 0.6.1 (2020-01-28)
|
72
|
+
|
73
|
+
- Fixed timeouts for PostgreSQL
|
74
|
+
|
75
|
+
## 0.6.0 (2020-01-24)
|
76
|
+
|
77
|
+
- Added `statement_timeout` and `lock_timeout`
|
78
|
+
- Adding a column with a non-null default value is safe in MySQL 8.0.12+ and MariaDB 10.3.2+
|
79
|
+
- Added `change_column_null` check for MySQL and MariaDB
|
80
|
+
- Added `auto_analyze` for MySQL and MariaDB
|
81
|
+
- Added `target_mysql_version` and `target_mariadb_version`
|
82
|
+
- Switched to `up` for backfilling
|
83
|
+
|
84
|
+
## 0.5.1 (2019-12-17)
|
85
|
+
|
86
|
+
- Fixed migration name in error messages
|
87
|
+
|
88
|
+
## 0.5.0 (2019-12-05)
|
89
|
+
|
90
|
+
- Added ability to disable checks
|
91
|
+
- Added Postgres-specific check for `change_column_null`
|
92
|
+
- Added optional remove index check
|
93
|
+
|
94
|
+
## 0.4.2 (2019-10-27)
|
95
|
+
|
96
|
+
- Allow `add_reference` with concurrent indexes
|
97
|
+
|
98
|
+
## 0.4.1 (2019-07-12)
|
99
|
+
|
100
|
+
- Added `target_postgresql_version`
|
101
|
+
- Added `unscoped` to backfill instructions
|
102
|
+
|
103
|
+
## 0.4.0 (2019-05-27)
|
104
|
+
|
105
|
+
- Added check for `add_foreign_key`
|
106
|
+
- Fixed instructions for adding default value with NOT NULL constraint
|
107
|
+
- Removed support for Rails 4.2
|
108
|
+
|
109
|
+
## 0.3.1 (2018-10-18)
|
2
110
|
|
3
111
|
- Fixed error with `remove_column` and `type` argument
|
4
112
|
- Improved message customization
|
5
113
|
|
6
|
-
## 0.3.0
|
114
|
+
## 0.3.0 (2018-10-15)
|
7
115
|
|
8
116
|
- Added support for custom checks
|
9
117
|
- Adding a column with a non-null default value is safe in Postgres 11+
|
10
118
|
- Added checks for `add_belongs_to`, `remove_belongs_to`, `remove_columns`, and `remove_reference`
|
11
119
|
- Customized messages
|
12
120
|
|
13
|
-
## 0.2.3
|
121
|
+
## 0.2.3 (2018-07-22)
|
14
122
|
|
15
123
|
- Added check for `change_column_null`
|
16
124
|
- Added support for alphabetize columns with Makara
|
17
125
|
- Fixed migration reversibility with `auto_analyze`
|
18
126
|
|
19
|
-
## 0.2.2
|
127
|
+
## 0.2.2 (2018-02-14)
|
20
128
|
|
21
129
|
- Friendlier output
|
22
130
|
- Better method of hooking into ActiveRecord
|
23
131
|
|
24
|
-
## 0.2.1
|
132
|
+
## 0.2.1 (2018-02-07)
|
25
133
|
|
26
134
|
- Recommend `disable_ddl_transaction!` over `commit_db_transaction`
|
27
135
|
- Suggest `jsonb` over `json` in Postgres 9.4+
|
28
136
|
- Changing `varchar` to `text` is safe in Postgres 9.1+
|
29
137
|
- Do not check number of columns for unique indexes
|
30
138
|
|
31
|
-
## 0.2.0
|
139
|
+
## 0.2.0 (2018-01-07)
|
32
140
|
|
33
141
|
- Added customizable error messages
|
34
142
|
- Updated instructions for adding a column with a default value
|
35
143
|
|
36
|
-
## 0.1.9
|
144
|
+
## 0.1.9 (2017-06-14)
|
37
145
|
|
38
146
|
- Added `start_after` option
|
39
147
|
|
40
|
-
## 0.1.8
|
148
|
+
## 0.1.8 (2017-05-31)
|
41
149
|
|
42
150
|
- Fixed error with `create_table`
|
43
151
|
- Added check for executing arbitrary SQL
|
44
152
|
|
45
|
-
## 0.1.7
|
153
|
+
## 0.1.7 (2017-05-29)
|
46
154
|
|
47
155
|
- Added check for `force` option with `create_table`
|
48
156
|
- Added `auto_analyze` option
|
49
157
|
|
50
|
-
## 0.1.6
|
158
|
+
## 0.1.6 (2017-03-23)
|
51
159
|
|
52
160
|
- Adding an index to a newly created table is now safe
|
53
161
|
|
54
|
-
## 0.1.5
|
162
|
+
## 0.1.5 (2016-07-23)
|
55
163
|
|
56
164
|
- Fixed error with Ruby 2.3 frozen strings
|
57
165
|
|
58
|
-
## 0.1.4
|
166
|
+
## 0.1.4 (2016-03-22)
|
59
167
|
|
60
168
|
- Added alphabetize columns
|
61
169
|
|
62
|
-
## 0.1.3
|
170
|
+
## 0.1.3 (2016-03-12)
|
63
171
|
|
64
172
|
- Disabled dangerous rake tasks in production
|
65
173
|
- Added ability to use `SAFETY_ASSURED` env var
|
66
174
|
|
67
|
-
## 0.1.2
|
175
|
+
## 0.1.2 (2016-02-24)
|
68
176
|
|
69
177
|
- Skip checks on down migrations and rollbacks
|
70
178
|
- Added check for indexes with more than 3 columns
|
71
179
|
|
72
|
-
## 0.1.1
|
180
|
+
## 0.1.1 (2015-11-29)
|
73
181
|
|
74
182
|
- Fixed `add_index` check for MySQL
|
75
183
|
|
76
|
-
## 0.1.0
|
184
|
+
## 0.1.0 (2015-11-22)
|
77
185
|
|
78
186
|
- First release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# Strong Migrations
|
2
2
|
|
3
|
-
Catch unsafe migrations
|
3
|
+
Catch unsafe migrations in development
|
4
|
+
|
5
|
+
✓ Detects potentially dangerous operations<br /> ✓ Prevents them from running by default<br /> ✓ Provides instructions on safer ways to do what you want
|
6
|
+
|
7
|
+
Supports for PostgreSQL, MySQL, and MariaDB
|
4
8
|
|
5
9
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
6
10
|
|
7
|
-
[![Build Status](https://
|
11
|
+
[![Build Status](https://github.com/ankane/strong_migrations/workflows/build/badge.svg?branch=master)](https://github.com/ankane/strong_migrations/actions)
|
8
12
|
|
9
13
|
## Installation
|
10
14
|
|
@@ -14,57 +18,130 @@ Add this line to your application’s Gemfile:
|
|
14
18
|
gem 'strong_migrations'
|
15
19
|
```
|
16
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
|
+
|
17
30
|
## How It Works
|
18
31
|
|
19
|
-
|
32
|
+
When you run a migration that’s potentially dangerous, you’ll see an error message like:
|
20
33
|
|
21
|
-
```
|
34
|
+
```txt
|
22
35
|
=== Dangerous operation detected #strong_migrations ===
|
23
36
|
|
24
|
-
|
37
|
+
Active Record caches attributes, which causes problems
|
25
38
|
when removing columns. Be sure to ignore the column:
|
26
39
|
|
27
40
|
class User < ApplicationRecord
|
28
|
-
self.ignored_columns = ["
|
41
|
+
self.ignored_columns = ["name"]
|
29
42
|
end
|
30
43
|
|
31
44
|
Deploy the code, then wrap this step in a safety_assured { ... } block.
|
32
45
|
|
33
|
-
class RemoveColumn < ActiveRecord::Migration[
|
46
|
+
class RemoveColumn < ActiveRecord::Migration[6.1]
|
34
47
|
def change
|
35
|
-
safety_assured { remove_column :users, :
|
48
|
+
safety_assured { remove_column :users, :name }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
An operation is classified as dangerous if it either:
|
54
|
+
|
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
|
+
- [adding a column with a default value](#adding-a-column-with-a-default-value)
|
64
|
+
- [backfilling data](#backfilling-data)
|
65
|
+
- [changing the type of a column](#changing-the-type-of-a-column)
|
66
|
+
- [renaming a column](#renaming-a-column)
|
67
|
+
- [renaming a table](#renaming-a-table)
|
68
|
+
- [creating a table with the force option](#creating-a-table-with-the-force-option)
|
69
|
+
- [adding a check constraint](#adding-a-check-constraint)
|
70
|
+
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
71
|
+
- [executing SQL directly](#executing-SQL-directly)
|
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 json column](#adding-a-json-column)
|
79
|
+
|
80
|
+
Best practices:
|
81
|
+
|
82
|
+
- [keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)
|
83
|
+
|
84
|
+
You can also add [custom checks](#custom-checks) or [disable specific checks](#disable-checks).
|
85
|
+
|
86
|
+
### Removing a column
|
87
|
+
|
88
|
+
#### Bad
|
89
|
+
|
90
|
+
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
|
94
|
+
def change
|
95
|
+
remove_column :users, :some_column
|
36
96
|
end
|
37
97
|
end
|
38
98
|
```
|
39
99
|
|
40
|
-
|
100
|
+
#### Good
|
41
101
|
|
42
|
-
|
102
|
+
1. Tell Active Record to ignore the column from its cache
|
43
103
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
- renaming a table
|
50
|
-
- creating a table with the `force` option
|
51
|
-
- adding an index non-concurrently (Postgres only)
|
52
|
-
- adding a `json` column to an existing table (Postgres only)
|
104
|
+
```ruby
|
105
|
+
class User < ApplicationRecord
|
106
|
+
self.ignored_columns = ["some_column"]
|
107
|
+
end
|
108
|
+
```
|
53
109
|
|
54
|
-
|
110
|
+
2. Deploy code
|
111
|
+
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
55
112
|
|
56
|
-
|
113
|
+
```ruby
|
114
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
|
115
|
+
def change
|
116
|
+
safety_assured { remove_column :users, :some_column }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
57
120
|
|
58
|
-
|
121
|
+
4. Deploy and run migration
|
59
122
|
|
60
123
|
### Adding a column with a default value
|
61
124
|
|
62
|
-
|
125
|
+
#### Bad
|
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.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
131
|
+
def change
|
132
|
+
add_column :users, :some_column, :text, default: "default_value"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe.
|
138
|
+
|
139
|
+
#### Good
|
63
140
|
|
64
141
|
Instead, add the column without a default value, then change the default.
|
65
142
|
|
66
143
|
```ruby
|
67
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[
|
144
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
68
145
|
def up
|
69
146
|
add_column :users, :some_column, :text
|
70
147
|
change_column_default :users, :some_column, "default_value"
|
@@ -76,64 +153,71 @@ class AddSomeColumnToUsers < ActiveRecord::Migration[5.2]
|
|
76
153
|
end
|
77
154
|
```
|
78
155
|
|
79
|
-
|
80
|
-
|
81
|
-
> With Postgres, this operation is safe as of Postgres 11
|
156
|
+
See the next section for how to backfill.
|
82
157
|
|
83
158
|
### Backfilling data
|
84
159
|
|
85
|
-
|
160
|
+
#### Bad
|
86
161
|
|
87
|
-
|
88
|
-
class BackfillSomeColumn < ActiveRecord::Migration[5.2]
|
89
|
-
disable_ddl_transaction!
|
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/).
|
90
163
|
|
164
|
+
```ruby
|
165
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
91
166
|
def change
|
92
|
-
|
93
|
-
User.
|
94
|
-
|
95
|
-
# Rails < 5
|
96
|
-
User.find_in_batches do |records|
|
97
|
-
User.where(id: records.map(&:id)).update_all some_column: "default_value"
|
98
|
-
end
|
167
|
+
add_column :users, :some_column, :text
|
168
|
+
User.update_all some_column: "default_value"
|
99
169
|
end
|
100
170
|
end
|
101
171
|
```
|
102
172
|
|
103
|
-
|
173
|
+
Also, running a single query to update data can cause issues for large tables.
|
104
174
|
|
105
|
-
|
175
|
+
#### Good
|
106
176
|
|
107
|
-
|
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!`.
|
108
178
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
self.ignored_columns = ["some_column"]
|
113
|
-
end
|
179
|
+
```ruby
|
180
|
+
class BackfillSomeColumn < ActiveRecord::Migration[6.1]
|
181
|
+
disable_ddl_transaction!
|
114
182
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
183
|
+
def up
|
184
|
+
User.unscoped.in_batches do |relation|
|
185
|
+
relation.update_all some_column: "default_value"
|
186
|
+
sleep(0.01) # throttle
|
119
187
|
end
|
120
188
|
end
|
121
|
-
|
189
|
+
end
|
190
|
+
```
|
122
191
|
|
123
|
-
|
124
|
-
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
192
|
+
### Changing the type of a column
|
125
193
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
194
|
+
#### Bad
|
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.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
|
200
|
+
def change
|
201
|
+
change_column :users, :some_column, :new_type
|
131
202
|
end
|
132
|
-
|
203
|
+
end
|
204
|
+
```
|
133
205
|
|
134
|
-
|
206
|
+
A few changes don’t require a table rewrite (and are safe) in Postgres:
|
207
|
+
|
208
|
+
- Increasing the length limit of a `varchar` column (or removing the limit)
|
209
|
+
- Changing a `varchar` column to a `text` column
|
210
|
+
- Changing a `text` column to a `varchar` column with no length limit
|
211
|
+
- Increasing the precision of a `decimal` or `numeric` column
|
212
|
+
- Making a `decimal` or `numeric` column unconstrained
|
213
|
+
- Changing between `timestamp` and `timestamptz` columns when session time zone is UTC in Postgres 12+
|
214
|
+
|
215
|
+
And a few in MySQL and MariaDB:
|
135
216
|
|
136
|
-
|
217
|
+
- Increasing the length limit of a `varchar` column from under 255 up to 255
|
218
|
+
- Increasing the length limit of a `varchar` column from over 255 to the max limit
|
219
|
+
|
220
|
+
#### Good
|
137
221
|
|
138
222
|
A safer approach is to:
|
139
223
|
|
@@ -144,10 +228,47 @@ A safer approach is to:
|
|
144
228
|
5. Stop writing to the old column
|
145
229
|
6. Drop the old column
|
146
230
|
|
147
|
-
|
231
|
+
### Renaming a column
|
232
|
+
|
233
|
+
#### Bad
|
234
|
+
|
235
|
+
Renaming a column that’s in use will cause errors in your application.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class RenameSomeColumn < ActiveRecord::Migration[6.1]
|
239
|
+
def change
|
240
|
+
rename_column :users, :some_column, :new_name
|
241
|
+
end
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
#### Good
|
246
|
+
|
247
|
+
A safer approach is to:
|
248
|
+
|
249
|
+
1. Create a new column
|
250
|
+
2. Write to both columns
|
251
|
+
3. Backfill data from the old column to the new column
|
252
|
+
4. Move reads from the old column to the new column
|
253
|
+
5. Stop writing to the old column
|
254
|
+
6. Drop the old column
|
148
255
|
|
149
256
|
### Renaming a table
|
150
257
|
|
258
|
+
#### Bad
|
259
|
+
|
260
|
+
Renaming a table that’s in use will cause errors in your application.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
|
264
|
+
def change
|
265
|
+
rename_table :users, :customers
|
266
|
+
end
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
#### Good
|
271
|
+
|
151
272
|
A safer approach is to:
|
152
273
|
|
153
274
|
1. Create a new table
|
@@ -157,12 +278,194 @@ A safer approach is to:
|
|
157
278
|
5. Stop writing to the old table
|
158
279
|
6. Drop the old table
|
159
280
|
|
160
|
-
###
|
281
|
+
### Creating a table with the force option
|
282
|
+
|
283
|
+
#### Bad
|
284
|
+
|
285
|
+
The `force` option can drop an existing table.
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class CreateUsers < ActiveRecord::Migration[6.1]
|
289
|
+
def change
|
290
|
+
create_table :users, force: true do |t|
|
291
|
+
# ...
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
#### Good
|
298
|
+
|
299
|
+
Create tables without the `force` option.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
class CreateUsers < ActiveRecord::Migration[6.1]
|
303
|
+
def change
|
304
|
+
create_table :users do |t|
|
305
|
+
# ...
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
```
|
310
|
+
|
311
|
+
If you intend to drop an existing table, run `drop_table` first.
|
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
|
+
|
355
|
+
### Setting NOT NULL on an existing column
|
356
|
+
|
357
|
+
:turtle: Safe by default available
|
358
|
+
|
359
|
+
#### Bad
|
360
|
+
|
361
|
+
Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
|
365
|
+
def change
|
366
|
+
change_column_null :users, :some_column, false
|
367
|
+
end
|
368
|
+
end
|
369
|
+
```
|
370
|
+
|
371
|
+
#### Good - Postgres
|
372
|
+
|
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:
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
389
|
+
def change
|
390
|
+
safety_assured do
|
391
|
+
execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
```
|
396
|
+
|
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:
|
414
|
+
|
415
|
+
```ruby
|
416
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
417
|
+
def change
|
418
|
+
safety_assured do
|
419
|
+
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
|
420
|
+
end
|
421
|
+
|
422
|
+
# in Postgres 12+, you can then safely set NOT NULL on the column
|
423
|
+
change_column_null :users, :some_column, false
|
424
|
+
safety_assured do
|
425
|
+
execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
```
|
430
|
+
|
431
|
+
#### Good - MySQL and MariaDB
|
432
|
+
|
433
|
+
[Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this.
|
434
|
+
|
435
|
+
### Executing SQL directly
|
436
|
+
|
437
|
+
Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
|
438
|
+
|
439
|
+
```ruby
|
440
|
+
class ExecuteSQL < ActiveRecord::Migration[6.1]
|
441
|
+
def change
|
442
|
+
safety_assured { execute "..." }
|
443
|
+
end
|
444
|
+
end
|
445
|
+
```
|
446
|
+
|
447
|
+
### Adding an index non-concurrently
|
448
|
+
|
449
|
+
:turtle: Safe by default available
|
450
|
+
|
451
|
+
#### Bad
|
452
|
+
|
453
|
+
In Postgres, adding an index non-concurrently blocks writes.
|
454
|
+
|
455
|
+
```ruby
|
456
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
457
|
+
def change
|
458
|
+
add_index :users, :some_column
|
459
|
+
end
|
460
|
+
end
|
461
|
+
```
|
462
|
+
|
463
|
+
#### Good
|
161
464
|
|
162
465
|
Add indexes concurrently.
|
163
466
|
|
164
467
|
```ruby
|
165
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[
|
468
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
166
469
|
disable_ddl_transaction!
|
167
470
|
|
168
471
|
def change
|
@@ -171,59 +474,201 @@ class AddSomeIndexToUsers < ActiveRecord::Migration[5.2]
|
|
171
474
|
end
|
172
475
|
```
|
173
476
|
|
174
|
-
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.
|
477
|
+
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.
|
175
478
|
|
176
|
-
|
479
|
+
With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
|
480
|
+
|
481
|
+
```sh
|
482
|
+
rails g index table column
|
483
|
+
```
|
484
|
+
|
485
|
+
### Adding a reference
|
486
|
+
|
487
|
+
:turtle: Safe by default available
|
488
|
+
|
489
|
+
#### Bad
|
490
|
+
|
491
|
+
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
177
492
|
|
178
493
|
```ruby
|
179
|
-
class
|
494
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
495
|
+
def change
|
496
|
+
add_reference :users, :city
|
497
|
+
end
|
498
|
+
end
|
499
|
+
```
|
500
|
+
|
501
|
+
#### Good
|
502
|
+
|
503
|
+
Make sure the index is added concurrently.
|
504
|
+
|
505
|
+
```ruby
|
506
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
180
507
|
disable_ddl_transaction!
|
181
508
|
|
182
509
|
def change
|
183
|
-
add_reference :users, :
|
184
|
-
|
510
|
+
add_reference :users, :city, index: {algorithm: :concurrently}
|
511
|
+
end
|
512
|
+
end
|
513
|
+
```
|
514
|
+
|
515
|
+
### Adding a foreign key
|
516
|
+
|
517
|
+
:turtle: Safe by default available
|
518
|
+
|
519
|
+
#### Bad
|
520
|
+
|
521
|
+
In Postgres, adding a foreign key blocks writes on both tables.
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
525
|
+
def change
|
526
|
+
add_foreign_key :users, :orders
|
185
527
|
end
|
186
528
|
end
|
187
529
|
```
|
188
530
|
|
189
|
-
|
531
|
+
or
|
190
532
|
|
191
|
-
|
533
|
+
```ruby
|
534
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
535
|
+
def change
|
536
|
+
add_reference :users, :order, foreign_key: true
|
537
|
+
end
|
538
|
+
end
|
539
|
+
```
|
192
540
|
|
193
|
-
|
541
|
+
#### Good
|
194
542
|
|
195
|
-
|
543
|
+
Add the foreign key without validating existing rows, then validate them in a separate migration.
|
196
544
|
|
197
|
-
|
545
|
+
For Rails 5.2+, use:
|
198
546
|
|
199
547
|
```ruby
|
200
|
-
class
|
201
|
-
|
548
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
549
|
+
def change
|
550
|
+
add_foreign_key :users, :orders, validate: false
|
551
|
+
end
|
552
|
+
end
|
553
|
+
```
|
554
|
+
|
555
|
+
Then:
|
556
|
+
|
557
|
+
```ruby
|
558
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
559
|
+
def change
|
560
|
+
validate_foreign_key :users, :orders
|
561
|
+
end
|
562
|
+
end
|
563
|
+
```
|
564
|
+
|
565
|
+
For Rails < 5.2, use:
|
566
|
+
|
567
|
+
```ruby
|
568
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
|
569
|
+
def change
|
570
|
+
safety_assured do
|
571
|
+
execute 'ALTER TABLE "users" ADD CONSTRAINT "fk_rails_c1e9b98e31" FOREIGN KEY ("order_id") REFERENCES "orders" ("id") NOT VALID'
|
572
|
+
end
|
573
|
+
end
|
202
574
|
end
|
203
575
|
```
|
204
576
|
|
205
|
-
Then
|
577
|
+
Then:
|
206
578
|
|
207
579
|
```ruby
|
208
|
-
class
|
580
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
|
209
581
|
def change
|
210
|
-
safety_assured
|
582
|
+
safety_assured do
|
583
|
+
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
|
584
|
+
end
|
211
585
|
end
|
212
586
|
end
|
213
587
|
```
|
214
588
|
|
589
|
+
### Adding a json column
|
590
|
+
|
591
|
+
#### Bad
|
592
|
+
|
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.
|
594
|
+
|
595
|
+
```ruby
|
596
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
|
597
|
+
def change
|
598
|
+
add_column :users, :properties, :json
|
599
|
+
end
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
#### Good
|
604
|
+
|
605
|
+
Use `jsonb` instead.
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
|
609
|
+
def change
|
610
|
+
add_column :users, :properties, :jsonb
|
611
|
+
end
|
612
|
+
end
|
613
|
+
```
|
614
|
+
|
615
|
+
### Keeping non-unique indexes to three columns or less
|
616
|
+
|
617
|
+
#### Bad
|
618
|
+
|
619
|
+
Adding a non-unique index with more than three columns rarely improves performance.
|
620
|
+
|
621
|
+
```ruby
|
622
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
623
|
+
def change
|
624
|
+
add_index :users, [:a, :b, :c, :d]
|
625
|
+
end
|
626
|
+
end
|
627
|
+
```
|
628
|
+
|
629
|
+
#### Good
|
630
|
+
|
631
|
+
Instead, start an index with columns that narrow down the results the most.
|
632
|
+
|
633
|
+
```ruby
|
634
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
635
|
+
def change
|
636
|
+
add_index :users, [:b, :d]
|
637
|
+
end
|
638
|
+
end
|
639
|
+
```
|
640
|
+
|
641
|
+
For Postgres, be sure to add them concurrently.
|
642
|
+
|
215
643
|
## Assuring Safety
|
216
644
|
|
217
|
-
To mark a step in the migration as safe, despite using method that might otherwise be dangerous, wrap it in a `safety_assured` block.
|
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.
|
218
646
|
|
219
647
|
```ruby
|
220
|
-
class MySafeMigration < ActiveRecord::Migration[
|
648
|
+
class MySafeMigration < ActiveRecord::Migration[6.1]
|
221
649
|
def change
|
222
650
|
safety_assured { remove_column :users, :some_column }
|
223
651
|
end
|
224
652
|
end
|
225
653
|
```
|
226
654
|
|
655
|
+
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.
|
656
|
+
|
657
|
+
## Safe by Default
|
658
|
+
|
659
|
+
Make operations safe by default.
|
660
|
+
|
661
|
+
- adding and removing an index
|
662
|
+
- adding a foreign key
|
663
|
+
- adding a check constraint
|
664
|
+
- setting NOT NULL on an existing column
|
665
|
+
|
666
|
+
Add to `config/initializers/strong_migrations.rb`:
|
667
|
+
|
668
|
+
```ruby
|
669
|
+
StrongMigrations.safe_by_default = true
|
670
|
+
```
|
671
|
+
|
227
672
|
## Custom Checks
|
228
673
|
|
229
674
|
Add your own custom checks with:
|
@@ -238,39 +683,34 @@ end
|
|
238
683
|
|
239
684
|
Use the `stop!` method to stop migrations.
|
240
685
|
|
241
|
-
|
242
|
-
|
243
|
-
To mark migrations as safe that were created before installing this gem, create an initializer with:
|
244
|
-
|
245
|
-
```ruby
|
246
|
-
StrongMigrations.start_after = 20170101000000
|
247
|
-
```
|
686
|
+
Note: Since `remove_column` always requires a `safety_assured` block, it’s not possible to add a custom check for `remove_column` operations.
|
248
687
|
|
249
|
-
|
688
|
+
## Opt-in Checks
|
250
689
|
|
251
|
-
|
690
|
+
### Removing an index non-concurrently
|
252
691
|
|
253
|
-
|
692
|
+
Postgres supports removing indexes concurrently, but removing them non-concurrently shouldn’t be an issue for most applications. You can enable this check with:
|
254
693
|
|
255
|
-
```
|
256
|
-
|
694
|
+
```ruby
|
695
|
+
StrongMigrations.enable_check(:remove_index)
|
257
696
|
```
|
258
697
|
|
259
|
-
##
|
698
|
+
## Disable Checks
|
260
699
|
|
261
|
-
|
700
|
+
Disable specific checks with:
|
262
701
|
|
263
702
|
```ruby
|
264
|
-
|
265
|
-
`git status db/migrate/ --porcelain`.present?
|
703
|
+
StrongMigrations.disable_check(:add_index)
|
266
704
|
```
|
267
705
|
|
268
|
-
|
706
|
+
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
|
707
|
+
|
708
|
+
## Down Migrations / Rollbacks
|
269
709
|
|
270
|
-
|
710
|
+
By default, checks are disabled when migrating down. Enable them with:
|
271
711
|
|
272
712
|
```ruby
|
273
|
-
|
713
|
+
StrongMigrations.check_down = true
|
274
714
|
```
|
275
715
|
|
276
716
|
## Custom Messages
|
@@ -283,7 +723,92 @@ StrongMigrations.error_messages[:add_column_default] = "Your custom instructions
|
|
283
723
|
|
284
724
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
|
285
725
|
|
286
|
-
##
|
726
|
+
## Migration Timeouts
|
727
|
+
|
728
|
+
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.
|
729
|
+
|
730
|
+
Create `config/initializers/strong_migrations.rb` with:
|
731
|
+
|
732
|
+
```ruby
|
733
|
+
StrongMigrations.lock_timeout = 10.seconds
|
734
|
+
StrongMigrations.statement_timeout = 1.hour
|
735
|
+
```
|
736
|
+
|
737
|
+
Or set the timeouts directly on the database user that runs migrations. For Postgres, use:
|
738
|
+
|
739
|
+
```sql
|
740
|
+
ALTER ROLE myuser SET lock_timeout = '10s';
|
741
|
+
ALTER ROLE myuser SET statement_timeout = '1h';
|
742
|
+
```
|
743
|
+
|
744
|
+
Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
|
745
|
+
|
746
|
+
## App Timeouts
|
747
|
+
|
748
|
+
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.
|
749
|
+
|
750
|
+
For Postgres:
|
751
|
+
|
752
|
+
```yml
|
753
|
+
production:
|
754
|
+
connect_timeout: 5
|
755
|
+
variables:
|
756
|
+
statement_timeout: 15s
|
757
|
+
lock_timeout: 10s
|
758
|
+
```
|
759
|
+
|
760
|
+
Note: If you use PgBouncer in transaction mode, you must set the statement and lock timeouts on the database user as shown above.
|
761
|
+
|
762
|
+
For MySQL:
|
763
|
+
|
764
|
+
```yml
|
765
|
+
production:
|
766
|
+
connect_timeout: 5
|
767
|
+
read_timeout: 5
|
768
|
+
write_timeout: 5
|
769
|
+
variables:
|
770
|
+
max_execution_time: 15000 # ms
|
771
|
+
lock_wait_timeout: 10 # sec
|
772
|
+
|
773
|
+
```
|
774
|
+
|
775
|
+
For MariaDB:
|
776
|
+
|
777
|
+
```yml
|
778
|
+
production:
|
779
|
+
connect_timeout: 5
|
780
|
+
read_timeout: 5
|
781
|
+
write_timeout: 5
|
782
|
+
variables:
|
783
|
+
max_statement_time: 15 # sec
|
784
|
+
lock_wait_timeout: 10 # sec
|
785
|
+
```
|
786
|
+
|
787
|
+
For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
|
788
|
+
|
789
|
+
## Existing Migrations
|
790
|
+
|
791
|
+
To mark migrations as safe that were created before installing this gem, create an initializer with:
|
792
|
+
|
793
|
+
```ruby
|
794
|
+
StrongMigrations.start_after = 20170101000000
|
795
|
+
```
|
796
|
+
|
797
|
+
Use the version from your latest migration.
|
798
|
+
|
799
|
+
## Target Version
|
800
|
+
|
801
|
+
If your development database version is different from production, you can specify the production version so the right checks run in development.
|
802
|
+
|
803
|
+
```ruby
|
804
|
+
StrongMigrations.target_version = 10 # or "8.0.12", "10.3.2", etc
|
805
|
+
```
|
806
|
+
|
807
|
+
The major version works well for Postgres, while the full version is recommended for MySQL and MariaDB.
|
808
|
+
|
809
|
+
For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
|
810
|
+
|
811
|
+
## Analyze Tables
|
287
812
|
|
288
813
|
Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
|
289
814
|
|
@@ -291,28 +816,41 @@ Analyze tables automatically (to update planner statistics) after an index is ad
|
|
291
816
|
StrongMigrations.auto_analyze = true
|
292
817
|
```
|
293
818
|
|
294
|
-
##
|
819
|
+
## Faster Migrations
|
295
820
|
|
296
|
-
|
821
|
+
Only dump the schema when adding a new migration. If you use Git, create an initializer with:
|
297
822
|
|
298
|
-
```
|
299
|
-
|
823
|
+
```ruby
|
824
|
+
ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
|
825
|
+
`git status db/migrate/ --porcelain`.present?
|
826
|
+
```
|
827
|
+
|
828
|
+
## Schema Sanity
|
829
|
+
|
830
|
+
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`:
|
831
|
+
|
832
|
+
```ruby
|
833
|
+
task "db:schema:dump": "strong_migrations:alphabetize_columns"
|
300
834
|
```
|
301
835
|
|
302
|
-
|
836
|
+
## Permissions
|
303
837
|
|
304
|
-
|
838
|
+
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.
|
305
839
|
|
306
|
-
|
840
|
+
## Smaller Projects
|
841
|
+
|
842
|
+
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.
|
307
843
|
|
308
844
|
## Additional Reading
|
309
845
|
|
310
846
|
- [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
|
311
|
-
- [
|
847
|
+
- [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
|
848
|
+
- [An Overview of DDL Algorithms in MySQL](https://mydbops.wordpress.com/2020/03/04/an-overview-of-ddl-algorithms-in-mysql-covers-mysql-8/)
|
849
|
+
- [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
|
312
850
|
|
313
851
|
## Credits
|
314
852
|
|
315
|
-
Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations).
|
853
|
+
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.
|
316
854
|
|
317
855
|
## Contributing
|
318
856
|
|
@@ -323,11 +861,18 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
323
861
|
- Write, clarify, or fix documentation
|
324
862
|
- Suggest or add new features
|
325
863
|
|
326
|
-
To get started with development
|
864
|
+
To get started with development:
|
327
865
|
|
328
866
|
```sh
|
329
867
|
git clone https://github.com/ankane/strong_migrations.git
|
330
868
|
cd strong_migrations
|
331
869
|
bundle install
|
870
|
+
|
871
|
+
# Postgres
|
872
|
+
createdb strong_migrations_test
|
332
873
|
bundle exec rake test
|
874
|
+
|
875
|
+
# MySQL and MariaDB
|
876
|
+
mysqladmin create strong_migrations_test
|
877
|
+
ADAPTER=mysql2 bundle exec rake test
|
333
878
|
```
|