strong_migrations 0.7.9 → 1.4.4

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.
@@ -9,8 +9,13 @@ module StrongMigrations
9
9
  def method_missing(method, *args)
10
10
  return super if is_a?(ActiveRecord::Schema)
11
11
 
12
- strong_migrations_checker.perform(method, *args) do
13
- super
12
+ # Active Record 7.0.2+ versioned schema
13
+ return super if defined?(ActiveRecord::Schema::Definition) && is_a?(ActiveRecord::Schema::Definition)
14
+
15
+ catch(:safe) do
16
+ strong_migrations_checker.perform(method, *args) do
17
+ super
18
+ end
14
19
  end
15
20
  end
16
21
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
@@ -0,0 +1,19 @@
1
+ module StrongMigrations
2
+ module Migrator
3
+ def ddl_transaction(migration, *args)
4
+ return super unless StrongMigrations.lock_timeout_retries > 0 && use_transaction?(migration)
5
+
6
+ # handle MigrationProxy class
7
+ migration = migration.send(:migration) if migration.respond_to?(:migration, true)
8
+
9
+ # retry migration since the entire transaction needs to be rerun
10
+ checker = migration.send(:strong_migrations_checker)
11
+ checker.retry_lock_timeouts(check_committed: true) do
12
+ # failed transaction reverts timeout, so need to re-apply
13
+ checker.timeouts_set = false
14
+
15
+ super(migration, *args)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -5,22 +5,22 @@ module StrongMigrations
5
5
  end
6
6
 
7
7
  # TODO check if invalid index with expected name exists and remove if needed
8
- def safe_add_index(table, columns, options)
8
+ def safe_add_index(*args, **options)
9
9
  disable_transaction
10
- @migration.add_index(table, columns, **options.merge(algorithm: :concurrently))
10
+ @migration.add_index(*args, **options.merge(algorithm: :concurrently))
11
11
  end
12
12
 
13
- def safe_remove_index(table, options)
13
+ def safe_remove_index(*args, **options)
14
14
  disable_transaction
15
- @migration.remove_index(table, **options.merge(algorithm: :concurrently))
15
+ @migration.remove_index(*args, **options.merge(algorithm: :concurrently))
16
16
  end
17
17
 
18
- def safe_add_reference(table, reference, options)
18
+ def safe_add_reference(table, reference, *args, **options)
19
19
  @migration.reversible do |dir|
20
20
  dir.up do
21
21
  disable_transaction
22
22
  foreign_key = options.delete(:foreign_key)
23
- @migration.add_reference(table, reference, **options)
23
+ @migration.add_reference(table, reference, *args, **options)
24
24
  if foreign_key
25
25
  # same as Active Record
26
26
  name =
@@ -43,50 +43,49 @@ module StrongMigrations
43
43
  end
44
44
  end
45
45
 
46
- def safe_add_foreign_key(from_table, to_table, options)
46
+ def safe_add_foreign_key(from_table, to_table, *args, **options)
47
47
  @migration.reversible do |dir|
48
48
  dir.up do
49
- @migration.add_foreign_key(from_table, to_table, **options.merge(validate: false))
49
+ @migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
50
50
  disable_transaction
51
- @migration.validate_foreign_key(from_table, to_table)
52
- end
53
- dir.down do
54
- @migration.remove_foreign_key(from_table, to_table)
55
- end
56
- end
57
- end
58
-
59
- def safe_add_foreign_key_code(from_table, to_table, add_code, validate_code)
60
- @migration.reversible do |dir|
61
- dir.up do
62
- @migration.safety_assured do
63
- @migration.execute(add_code)
64
- disable_transaction
65
- @migration.execute(validate_code)
51
+ validate_options = options.slice(:column, :name)
52
+ if ActiveRecord::VERSION::MAJOR >= 6
53
+ @migration.validate_foreign_key(from_table, to_table, **validate_options)
54
+ else
55
+ @migration.validate_foreign_key(from_table, validate_options.any? ? validate_options : to_table)
66
56
  end
67
57
  end
68
58
  dir.down do
69
- @migration.remove_foreign_key(from_table, to_table)
59
+ remove_options = options.slice(:column, :name)
60
+ if ActiveRecord::VERSION::MAJOR >= 6
61
+ @migration.remove_foreign_key(from_table, to_table, **remove_options)
62
+ else
63
+ @migration.remove_foreign_key(from_table, remove_options.any? ? remove_options : to_table)
64
+ end
70
65
  end
71
66
  end
72
67
  end
73
68
 
74
- def safe_add_check_constraint(table, expression, add_options, validate_options)
69
+ def safe_add_check_constraint(table, expression, *args, add_options, validate_options)
75
70
  @migration.reversible do |dir|
76
71
  dir.up do
77
- @migration.add_check_constraint(table, expression, **add_options)
72
+ @migration.add_check_constraint(table, expression, *args, **add_options)
78
73
  disable_transaction
79
74
  @migration.validate_check_constraint(table, **validate_options)
80
75
  end
81
76
  dir.down do
82
- @migration.remove_check_constraint(table, expression, **add_options)
77
+ @migration.remove_check_constraint(table, expression, **add_options.except(:validate))
83
78
  end
84
79
  end
85
80
  end
86
81
 
87
- def safe_change_column_null(add_code, validate_code, change_args, remove_code)
82
+ def safe_change_column_null(add_code, validate_code, change_args, remove_code, default)
88
83
  @migration.reversible do |dir|
89
84
  dir.up do
85
+ unless default.nil?
86
+ raise Error, "default value not supported yet with safe_by_default"
87
+ end
88
+
90
89
  @migration.safety_assured do
91
90
  @migration.execute(add_code)
92
91
  disable_transaction
@@ -0,0 +1,21 @@
1
+ module StrongMigrations
2
+ module SchemaDumper
3
+ def initialize(connection, *args, **options)
4
+ return super unless StrongMigrations.alphabetize_schema
5
+
6
+ super(WrappedConnection.new(connection), *args, **options)
7
+ end
8
+ end
9
+
10
+ class WrappedConnection
11
+ delegate_missing_to :@connection
12
+
13
+ def initialize(connection)
14
+ @connection = connection
15
+ end
16
+
17
+ def columns(*args, **options)
18
+ @connection.columns(*args, **options).sort_by(&:name)
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "0.7.9"
2
+ VERSION = "1.4.4"
3
3
  end
@@ -1,253 +1,59 @@
1
1
  # dependencies
2
2
  require "active_support"
3
3
 
4
+ # adapters
5
+ require_relative "strong_migrations/adapters/abstract_adapter"
6
+ require_relative "strong_migrations/adapters/mysql_adapter"
7
+ require_relative "strong_migrations/adapters/mariadb_adapter"
8
+ require_relative "strong_migrations/adapters/postgresql_adapter"
9
+
4
10
  # modules
5
- require "strong_migrations/safe_methods"
6
- require "strong_migrations/checker"
7
- require "strong_migrations/database_tasks"
8
- require "strong_migrations/migration"
9
- require "strong_migrations/version"
11
+ require_relative "strong_migrations/checks"
12
+ require_relative "strong_migrations/safe_methods"
13
+ require_relative "strong_migrations/checker"
14
+ require_relative "strong_migrations/database_tasks"
15
+ require_relative "strong_migrations/migration"
16
+ require_relative "strong_migrations/migrator"
17
+ require_relative "strong_migrations/version"
10
18
 
11
19
  # integrations
12
- require "strong_migrations/railtie" if defined?(Rails)
20
+ require_relative "strong_migrations/railtie" if defined?(Rails)
13
21
 
14
22
  module StrongMigrations
15
23
  class Error < StandardError; end
16
24
  class UnsafeMigration < Error; end
25
+ class UnsupportedVersion < Error; end
17
26
 
18
27
  class << self
19
28
  attr_accessor :auto_analyze, :start_after, :checks, :error_messages,
20
29
  :target_postgresql_version, :target_mysql_version, :target_mariadb_version,
21
30
  :enabled_checks, :lock_timeout, :statement_timeout, :check_down, :target_version,
22
- :safe_by_default
31
+ :safe_by_default, :target_sql_mode, :lock_timeout_retries, :lock_timeout_retry_delay,
32
+ :alphabetize_schema
23
33
  attr_writer :lock_timeout_limit
24
34
  end
25
35
  self.auto_analyze = false
26
36
  self.start_after = 0
37
+ self.lock_timeout_retries = 0
38
+ self.lock_timeout_retry_delay = 10 # seconds
27
39
  self.checks = []
28
40
  self.safe_by_default = false
29
- self.error_messages = {
30
- add_column_default:
31
- "Adding a column with a non-null default blocks %{rewrite_blocks} while the entire table is rewritten.
32
- Instead, add the column without a default value, then change the default.
33
-
34
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
35
- def up
36
- %{add_command}
37
- %{change_command}
38
- end
39
-
40
- def down
41
- %{remove_command}
42
- end
43
- end
44
-
45
- Then backfill the existing rows in the Rails console or a separate migration with disable_ddl_transaction!.
46
-
47
- class Backfill%{migration_name} < ActiveRecord::Migration%{migration_suffix}
48
- disable_ddl_transaction!
49
-
50
- def up
51
- %{code}
52
- end
53
- end",
54
-
55
- add_column_json:
56
- "There's no equality operator for the json column type, which can cause errors for
57
- existing SELECT DISTINCT queries in your application. Use jsonb instead.
58
-
59
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
60
- def change
61
- %{command}
62
- end
63
- end",
64
-
65
- change_column:
66
- "Changing the type of an existing column blocks %{rewrite_blocks}
67
- while the entire table is rewritten. A safer approach is to:
68
-
69
- 1. Create a new column
70
- 2. Write to both columns
71
- 3. Backfill data from the old column to the new column
72
- 4. Move reads from the old column to the new column
73
- 5. Stop writing to the old column
74
- 6. Drop the old column",
75
-
76
- change_column_with_not_null:
77
- "Changing the type is safe, but setting NOT NULL is not.",
78
-
79
- remove_column: "Active Record caches attributes, which causes problems
80
- when removing columns. Be sure to ignore the column%{column_suffix}:
81
-
82
- class %{model} < %{base_model}
83
- %{code}
84
- end
85
-
86
- Deploy the code, then wrap this step in a safety_assured { ... } block.
87
-
88
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
89
- def change
90
- safety_assured { %{command} }
91
- end
92
- end",
93
-
94
- rename_column:
95
- "Renaming a column that's in use will cause errors
96
- in your application. A safer approach is to:
97
-
98
- 1. Create a new column
99
- 2. Write to both columns
100
- 3. Backfill data from the old column to new column
101
- 4. Move reads from the old column to the new column
102
- 5. Stop writing to the old column
103
- 6. Drop the old column",
104
-
105
- rename_table:
106
- "Renaming a table that's in use will cause errors
107
- in your application. A safer approach is to:
108
-
109
- 1. Create a new table. Don't forget to recreate indexes from the old table
110
- 2. Write to both tables
111
- 3. Backfill data from the old table to new table
112
- 4. Move reads from the old table to the new table
113
- 5. Stop writing to the old table
114
- 6. Drop the old table",
115
-
116
- add_reference:
117
- "%{headline} Instead, use:
118
-
119
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
120
- disable_ddl_transaction!
121
-
122
- def change
123
- %{command}
124
- end
125
- end",
126
-
127
- add_index:
128
- "Adding an index non-concurrently blocks writes. Instead, use:
129
-
130
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
131
- disable_ddl_transaction!
132
-
133
- def change
134
- %{command}
135
- end
136
- end",
137
-
138
- remove_index:
139
- "Removing an index non-concurrently blocks writes. Instead, use:
140
-
141
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
142
- disable_ddl_transaction!
143
-
144
- def change
145
- %{command}
146
- end
147
- end",
148
-
149
- add_index_columns:
150
- "Adding a non-unique index with more than three columns rarely improves performance.
151
- Instead, start an index with columns that narrow down the results the most.",
152
-
153
- change_table:
154
- "Strong Migrations does not support inspecting what happens inside a
155
- change_table block, so cannot help you here. Please make really sure that what
156
- you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
157
-
158
- create_table:
159
- "The force option will destroy existing tables.
160
- If this is intended, drop the existing table first.
161
- Otherwise, remove the force option.",
162
-
163
- execute:
164
- "Strong Migrations does not support inspecting what happens inside an
165
- execute call, so cannot help you here. Please make really sure that what
166
- you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
167
-
168
- change_column_null:
169
- "Passing a default value to change_column_null runs a single UPDATE query,
170
- which can cause downtime. Instead, backfill the existing rows in the
171
- Rails console or a separate migration with disable_ddl_transaction!.
172
-
173
- class Backfill%{migration_name} < ActiveRecord::Migration%{migration_suffix}
174
- disable_ddl_transaction!
175
-
176
- def up
177
- %{code}
178
- end
179
- end",
180
-
181
- change_column_null_postgresql:
182
- "Setting NOT NULL on an existing column blocks reads and writes while every row is checked.
183
- Instead, add a check constraint and validate it in a separate migration.
184
-
185
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
186
- def change
187
- %{add_constraint_code}
188
- end
189
- end
190
-
191
- class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
192
- def change
193
- %{validate_constraint_code}
194
- end
195
- end",
196
-
197
- change_column_null_mysql:
198
- "Setting NOT NULL on an existing column is not safe with your database engine.",
199
-
200
- add_foreign_key:
201
- "Adding a foreign key blocks writes on both tables. Instead,
202
- add the foreign key without validating existing rows,
203
- then validate them in a separate migration.
204
-
205
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
206
- def change
207
- %{add_foreign_key_code}
208
- end
209
- end
210
-
211
- class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
212
- def change
213
- %{validate_foreign_key_code}
214
- end
215
- end",
216
-
217
- validate_foreign_key:
218
- "Validating a foreign key while writes are blocked is dangerous.
219
- Use disable_ddl_transaction! or a separate migration.",
220
-
221
- add_check_constraint:
222
- "Adding a check constraint key blocks reads and writes while every row is checked.
223
- Instead, add the check constraint without validating existing rows,
224
- then validate them in a separate migration.
225
-
226
- class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
227
- def change
228
- %{add_check_constraint_code}
229
- end
230
- end
231
-
232
- class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
233
- def change
234
- %{validate_check_constraint_code}
235
- end
236
- end",
237
-
238
- add_check_constraint_mysql:
239
- "Adding a check constraint to an existing table is not safe with your database engine.",
240
-
241
- validate_check_constraint:
242
- "Validating a check constraint while writes are blocked is dangerous.
243
- Use disable_ddl_transaction! or a separate migration."
244
- }
245
- self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
246
41
  self.check_down = false
42
+ self.alphabetize_schema = false
247
43
 
248
44
  # private
249
45
  def self.developer_env?
250
- defined?(Rails) && (Rails.env.development? || Rails.env.test?)
46
+ env == "development" || env == "test"
47
+ end
48
+
49
+ # private
50
+ def self.env
51
+ if defined?(Rails.env)
52
+ Rails.env
53
+ else
54
+ # default to production for safety
55
+ ENV["RACK_ENV"] || "production"
56
+ end
251
57
  end
252
58
 
253
59
  def self.lock_timeout_limit
@@ -279,10 +85,17 @@ Use disable_ddl_transaction! or a separate migration."
279
85
  end
280
86
  end
281
87
 
88
+ # load error messages
89
+ require_relative "strong_migrations/error_messages"
90
+
282
91
  ActiveSupport.on_load(:active_record) do
283
92
  ActiveRecord::Migration.prepend(StrongMigrations::Migration)
93
+ ActiveRecord::Migrator.prepend(StrongMigrations::Migrator)
284
94
 
285
95
  if defined?(ActiveRecord::Tasks::DatabaseTasks)
286
96
  ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(StrongMigrations::DatabaseTasks)
287
97
  end
98
+
99
+ require_relative "strong_migrations/schema_dumper"
100
+ ActiveRecord::SchemaDumper.prepend(StrongMigrations::SchemaDumper)
288
101
  end
@@ -1,14 +1,9 @@
1
1
  namespace :strong_migrations do
2
- # https://www.pgrs.net/2008/03/13/alphabetize-schema-rb-columns/
2
+ # https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/
3
3
  task :alphabetize_columns do
4
4
  $stderr.puts "Dumping schema"
5
5
  ActiveRecord::Base.logger.level = Logger::INFO
6
6
 
7
- require "strong_migrations/alphabetize_columns"
8
- ActiveRecord::Base.connection.class.prepend StrongMigrations::AlphabetizeColumns
9
- if ActiveRecord::ConnectionAdapters.const_defined?('PostGISAdapter')
10
- ActiveRecord::ConnectionAdapters::PostGISAdapter.prepend StrongMigrations::AlphabetizeColumns
11
- end
12
- ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend StrongMigrations::AlphabetizeColumns
7
+ StrongMigrations.alphabetize_schema = true
13
8
  end
14
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 1.4.4
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: 2021-12-16 00:00:00.000000000 Z
13
+ date: 2023-03-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '5'
21
+ version: '5.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '5'
28
+ version: '5.2'
29
29
  description:
30
30
  email:
31
31
  - andrew@ankane.org
@@ -41,12 +41,19 @@ files:
41
41
  - lib/generators/strong_migrations/install_generator.rb
42
42
  - lib/generators/strong_migrations/templates/initializer.rb.tt
43
43
  - lib/strong_migrations.rb
44
- - lib/strong_migrations/alphabetize_columns.rb
44
+ - lib/strong_migrations/adapters/abstract_adapter.rb
45
+ - lib/strong_migrations/adapters/mariadb_adapter.rb
46
+ - lib/strong_migrations/adapters/mysql_adapter.rb
47
+ - lib/strong_migrations/adapters/postgresql_adapter.rb
45
48
  - lib/strong_migrations/checker.rb
49
+ - lib/strong_migrations/checks.rb
46
50
  - lib/strong_migrations/database_tasks.rb
51
+ - lib/strong_migrations/error_messages.rb
47
52
  - lib/strong_migrations/migration.rb
53
+ - lib/strong_migrations/migrator.rb
48
54
  - lib/strong_migrations/railtie.rb
49
55
  - lib/strong_migrations/safe_methods.rb
56
+ - lib/strong_migrations/schema_dumper.rb
50
57
  - lib/strong_migrations/version.rb
51
58
  - lib/tasks/strong_migrations.rake
52
59
  homepage: https://github.com/ankane/strong_migrations
@@ -61,14 +68,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
68
  requirements:
62
69
  - - ">="
63
70
  - !ruby/object:Gem::Version
64
- version: '2.4'
71
+ version: '2.6'
65
72
  required_rubygems_version: !ruby/object:Gem::Requirement
66
73
  requirements:
67
74
  - - ">="
68
75
  - !ruby/object:Gem::Version
69
76
  version: '0'
70
77
  requirements: []
71
- rubygems_version: 3.1.6
78
+ rubygems_version: 3.4.6
72
79
  signing_key:
73
80
  specification_version: 4
74
81
  summary: Catch unsafe migrations in development
@@ -1,11 +0,0 @@
1
- module StrongMigrations
2
- module AlphabetizeColumns
3
- def columns(*args)
4
- super.sort_by(&:name)
5
- end
6
-
7
- def extensions(*args)
8
- super.sort
9
- end
10
- end
11
- end