strong_migrations 1.4.4 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +53 -0
- data/lib/strong_migrations/checker.rb +3 -0
- data/lib/strong_migrations/checks.rb +24 -1
- data/lib/strong_migrations/error_messages.rb +10 -0
- data/lib/strong_migrations/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23737504a1d3beb08bf2412b161b8e59a8a620b4f4d993f11fe931e7f1772452
|
4
|
+
data.tar.gz: 993dc5f9d78cba148540a22772c18ed115ea6ae8075dd209cc3a869cc2a647a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e906dc1c79d12a72a7d0e6fb4ca87319095d3b0a39f2da1dbd08b453d19e3215892745c86f80bf9ea26bbf07173d87022aab21b424a881f24744b65396fcd9bc
|
7
|
+
data.tar.gz: 56a0dadcb236f09a1a7d5450456ae343ac8cb6662b65f983c28b4268edad6e7cbecfa47c86ebb0d1da72dcc1b305e7196521811a48838c25beb1119846a2b928
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 1.6.0 (2023-07-22)
|
2
|
+
|
3
|
+
- Added check for `change_column_default`
|
4
|
+
|
5
|
+
## 1.5.0 (2023-07-02)
|
6
|
+
|
7
|
+
- Added check for `add_column` with stored generated columns
|
8
|
+
- Fixed `add_reference` with `foreign_key` and `index: false`
|
9
|
+
|
1
10
|
## 1.4.4 (2023-03-08)
|
2
11
|
|
3
12
|
- Fixed `add_foreign_key` with `name` and `column` options with `safe_by_default`
|
data/README.md
CHANGED
@@ -62,6 +62,7 @@ Potentially dangerous operations:
|
|
62
62
|
- [removing a column](#removing-a-column)
|
63
63
|
- [adding a column with a default value](#adding-a-column-with-a-default-value)
|
64
64
|
- [backfilling data](#backfilling-data)
|
65
|
+
- [adding a stored generated column](#adding-a-stored-generated-column)
|
65
66
|
- [changing the type of a column](#changing-the-type-of-a-column)
|
66
67
|
- [renaming a column](#renaming-a-column)
|
67
68
|
- [renaming a table](#renaming-a-table)
|
@@ -78,6 +79,10 @@ Postgres-specific checks:
|
|
78
79
|
- [adding a json column](#adding-a-json-column)
|
79
80
|
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
80
81
|
|
82
|
+
Config-specific checks:
|
83
|
+
|
84
|
+
- [changing the default value of a column](#changing-the-default-value-of-a-column)
|
85
|
+
|
81
86
|
Best practices:
|
82
87
|
|
83
88
|
- [keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)
|
@@ -191,6 +196,24 @@ class BackfillSomeColumn < ActiveRecord::Migration[7.0]
|
|
191
196
|
end
|
192
197
|
```
|
193
198
|
|
199
|
+
### Adding a stored generated column
|
200
|
+
|
201
|
+
#### Bad
|
202
|
+
|
203
|
+
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.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
|
207
|
+
def change
|
208
|
+
add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
#### Good
|
214
|
+
|
215
|
+
Add a non-generated column and use callbacks or triggers instead (or a virtual generated column with MySQL and MariaDB).
|
216
|
+
|
194
217
|
### Changing the type of a column
|
195
218
|
|
196
219
|
#### Bad
|
@@ -609,6 +632,36 @@ class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
|
609
632
|
end
|
610
633
|
```
|
611
634
|
|
635
|
+
### Changing the default value of a column
|
636
|
+
|
637
|
+
#### Bad
|
638
|
+
|
639
|
+
Rails < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
|
640
|
+
|
641
|
+
```ruby
|
642
|
+
class ChangeSomeColumnDefault < ActiveRecord::Migration[6.1]
|
643
|
+
def change
|
644
|
+
change_column_default :users, :some_column, from: "old", to: "new"
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
User.create!(some_column: "old") # can insert "new"
|
649
|
+
```
|
650
|
+
|
651
|
+
#### Good
|
652
|
+
|
653
|
+
Disable partial writes in `config/application.rb`. For Rails < 7, use:
|
654
|
+
|
655
|
+
```ruby
|
656
|
+
config.active_record.partial_writes = false
|
657
|
+
```
|
658
|
+
|
659
|
+
For Rails 7, use:
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
config.active_record.partial_inserts = false
|
663
|
+
```
|
664
|
+
|
612
665
|
### Keeping non-unique indexes to three columns or less
|
613
666
|
|
614
667
|
#### Bad
|
@@ -8,6 +8,7 @@ module StrongMigrations
|
|
8
8
|
def initialize(migration)
|
9
9
|
@migration = migration
|
10
10
|
@new_tables = []
|
11
|
+
@new_columns = []
|
11
12
|
@safe = false
|
12
13
|
@timeouts_set = false
|
13
14
|
@committed = false
|
@@ -46,6 +47,8 @@ module StrongMigrations
|
|
46
47
|
check_add_reference(method, *args)
|
47
48
|
when :change_column
|
48
49
|
check_change_column(*args)
|
50
|
+
when :change_column_default
|
51
|
+
check_change_column_default(*args)
|
49
52
|
when :change_column_null
|
50
53
|
check_change_column_null(*args)
|
51
54
|
when :change_table
|
@@ -32,6 +32,9 @@ module StrongMigrations
|
|
32
32
|
table, column, type = args
|
33
33
|
default = options[:default]
|
34
34
|
|
35
|
+
# keep track of new columns of change_column_default check
|
36
|
+
@new_columns << [table.to_s, column.to_s]
|
37
|
+
|
35
38
|
# Check key since DEFAULT NULL behaves differently from no default
|
36
39
|
#
|
37
40
|
# Also, Active Record has special case for uuid columns that allows function default values
|
@@ -71,6 +74,10 @@ Then add the NOT NULL constraint in separate migrations."
|
|
71
74
|
raise_error :add_column_json,
|
72
75
|
command: command_str("add_column", [table, column, :jsonb, options])
|
73
76
|
end
|
77
|
+
|
78
|
+
if type.to_s == "virtual" && options[:stored]
|
79
|
+
raise_error :add_column_generated_stored, rewrite_blocks: adapter.rewrite_blocks
|
80
|
+
end
|
74
81
|
end
|
75
82
|
|
76
83
|
def check_add_exclusion_constraint(*args)
|
@@ -147,7 +154,7 @@ Then add the NOT NULL constraint in separate migrations."
|
|
147
154
|
if bad_index || options[:foreign_key]
|
148
155
|
if index_value.is_a?(Hash)
|
149
156
|
options[:index] = options[:index].merge(algorithm: :concurrently)
|
150
|
-
|
157
|
+
elsif index_value
|
151
158
|
options = options.merge(index: {algorithm: :concurrently})
|
152
159
|
end
|
153
160
|
|
@@ -194,6 +201,18 @@ Then add the foreign key in separate migrations."
|
|
194
201
|
raise_error :change_column, rewrite_blocks: adapter.rewrite_blocks unless safe
|
195
202
|
end
|
196
203
|
|
204
|
+
def check_change_column_default(*args)
|
205
|
+
table, column, _default_or_changes = args
|
206
|
+
|
207
|
+
# just check ActiveRecord::Base, even though can override on model
|
208
|
+
partial_inserts = ar_version >= 7 ? ActiveRecord::Base.partial_inserts : ActiveRecord::Base.partial_writes
|
209
|
+
|
210
|
+
if partial_inserts && !new_column?(table, column)
|
211
|
+
raise_error :change_column_default,
|
212
|
+
config: ar_version >= 7 ? "partial_inserts" : "partial_writes"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
197
216
|
def check_change_column_null(*args)
|
198
217
|
table, column, null, default = args
|
199
218
|
if !null
|
@@ -456,5 +475,9 @@ Then add the foreign key in separate migrations."
|
|
456
475
|
def new_table?(table)
|
457
476
|
@new_tables.include?(table.to_s)
|
458
477
|
end
|
478
|
+
|
479
|
+
def new_column?(table, column)
|
480
|
+
new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
|
481
|
+
end
|
459
482
|
end
|
460
483
|
end
|
@@ -50,6 +50,9 @@ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
|
|
50
50
|
end
|
51
51
|
end",
|
52
52
|
|
53
|
+
add_column_generated_stored:
|
54
|
+
"Adding a stored generated column blocks %{rewrite_blocks} while the entire table is rewritten.",
|
55
|
+
|
53
56
|
change_column:
|
54
57
|
"Changing the type of an existing column blocks %{rewrite_blocks}
|
55
58
|
while the entire table is rewritten. A safer approach is to:
|
@@ -158,6 +161,13 @@ Otherwise, remove the force option.",
|
|
158
161
|
execute call, so cannot help you here. Please make really sure that what
|
159
162
|
you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
|
160
163
|
|
164
|
+
change_column_default:
|
165
|
+
"Partial writes are enabled, which can cause incorrect values
|
166
|
+
to be inserted when changing the default value of a column.
|
167
|
+
Disable partial writes in config/application.rb:
|
168
|
+
|
169
|
+
config.active_record.%{config} = false",
|
170
|
+
|
161
171
|
change_column_null:
|
162
172
|
"Passing a default value to change_column_null runs a single UPDATE query,
|
163
173
|
which can cause downtime. Instead, backfill the existing rows in the
|
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: 1.
|
4
|
+
version: 1.6.0
|
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: 2023-
|
13
|
+
date: 2023-07-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
requirements: []
|
78
|
-
rubygems_version: 3.4.
|
78
|
+
rubygems_version: 3.4.10
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: Catch unsafe migrations in development
|