strong_migrations 1.8.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.
@@ -1,10 +1,9 @@
1
1
  module StrongMigrations
2
2
  module SafeMethods
3
3
  def safe_by_default_method?(method)
4
- StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
4
+ StrongMigrations.safe_by_default && !version_safe? && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
5
5
  end
6
6
 
7
- # TODO check if invalid index with expected name exists and remove if needed
8
7
  def safe_add_index(*args, **options)
9
8
  disable_transaction
10
9
  @migration.add_index(*args, **options.merge(algorithm: :concurrently))
@@ -47,22 +46,16 @@ module StrongMigrations
47
46
  def safe_add_foreign_key(from_table, to_table, *args, **options)
48
47
  @migration.reversible do |dir|
49
48
  dir.up do
50
- @migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
51
- disable_transaction
52
- validate_options = options.slice(:column, :name)
53
- if ActiveRecord::VERSION::MAJOR >= 6
54
- @migration.validate_foreign_key(from_table, to_table, **validate_options)
55
- else
56
- @migration.validate_foreign_key(from_table, validate_options.any? ? validate_options : to_table)
49
+ # validate option is unintentionally ignored for Active Record < 7.1
50
+ # https://github.com/rails/rails/pull/45896
51
+ if !connection.foreign_key_exists?(from_table, to_table, **options.merge(validate: false))
52
+ @migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
57
53
  end
54
+ disable_transaction
55
+ @migration.validate_foreign_key(from_table, to_table, **options.slice(:column, :name))
58
56
  end
59
57
  dir.down do
60
- remove_options = options.slice(:column, :name)
61
- if ActiveRecord::VERSION::MAJOR >= 6
62
- @migration.remove_foreign_key(from_table, to_table, **remove_options)
63
- else
64
- @migration.remove_foreign_key(from_table, remove_options.any? ? remove_options : to_table)
65
- end
58
+ @migration.remove_foreign_key(from_table, to_table, **options.slice(:column, :name))
66
59
  end
67
60
  end
68
61
  end
@@ -70,7 +63,10 @@ module StrongMigrations
70
63
  def safe_add_check_constraint(table, expression, *args, add_options, validate_options)
71
64
  @migration.reversible do |dir|
72
65
  dir.up do
73
- @migration.add_check_constraint(table, expression, *args, **add_options)
66
+ # only skip invalid constraints
67
+ unless connection.check_constraints(table).any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
68
+ @migration.add_check_constraint(table, expression, *args, **add_options)
69
+ end
74
70
  disable_transaction
75
71
  @migration.validate_check_constraint(table, **validate_options)
76
72
  end
@@ -80,35 +76,60 @@ module StrongMigrations
80
76
  end
81
77
  end
82
78
 
83
- def safe_change_column_null(add_code, validate_code, change_args, remove_code, default)
79
+ def safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
84
80
  @migration.reversible do |dir|
85
81
  dir.up do
86
82
  unless default.nil?
87
- raise Error, "default value not supported yet with safe_by_default"
88
- end
83
+ # TODO search for parent model if needed
84
+ if connection.pool != ActiveRecord::Base.connection_pool
85
+ raise_error :change_column_null,
86
+ code: backfill_code(table, column, default)
87
+ end
88
+
89
+ model =
90
+ Class.new(ActiveRecord::Base) do
91
+ self.table_name = table
89
92
 
90
- @migration.safety_assured do
91
- @migration.execute(add_code)
93
+ def self.to_s
94
+ "Backfill"
95
+ end
96
+ end
97
+
98
+ update_sql =
99
+ model.connection_pool.with_connection do |c|
100
+ quoted_column = c.quote_column_name(column)
101
+ quoted_default = c.quote_default_expression(default, c.send(:column_for, table, column))
102
+ "#{quoted_column} = #{quoted_default}"
103
+ end
104
+
105
+ @migration.say("Backfilling default")
92
106
  disable_transaction
93
- @migration.execute(validate_code)
94
- end
95
- if change_args
96
- @migration.change_column_null(*change_args)
97
- @migration.safety_assured do
98
- @migration.execute(remove_code)
107
+ model.unscoped.in_batches(of: 10000) do |relation|
108
+ relation.where(column => nil).update_all(update_sql)
109
+ sleep(0.01)
99
110
  end
100
111
  end
112
+
113
+ add_options = add_args.extract_options!
114
+ validate_options = validate_args.extract_options!
115
+ remove_options = remove_args.extract_options!
116
+
117
+ # only skip invalid constraints
118
+ unless constraints.any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
119
+ @migration.add_check_constraint(*add_args, **add_options)
120
+ end
121
+ disable_transaction
122
+
123
+ connection.begin_db_transaction
124
+ @migration.validate_check_constraint(*validate_args, **validate_options)
125
+ @migration.change_column_null(*change_args)
126
+ @migration.remove_check_constraint(*remove_args, **remove_options)
127
+ connection.commit_db_transaction
101
128
  end
102
129
  dir.down do
103
- if change_args
104
- down_args = change_args.dup
105
- down_args[2] = true
106
- @migration.change_column_null(*down_args)
107
- else
108
- @migration.safety_assured do
109
- @migration.execute(remove_code)
110
- end
111
- end
130
+ down_args = change_args.dup
131
+ down_args[2] = true
132
+ @migration.change_column_null(*down_args)
112
133
  end
113
134
  end
114
135
  end
@@ -117,13 +138,13 @@ module StrongMigrations
117
138
  # so just commit at start
118
139
  def disable_transaction
119
140
  if in_transaction? && !transaction_disabled
120
- @migration.connection.commit_db_transaction
141
+ connection.commit_db_transaction
121
142
  self.transaction_disabled = true
122
143
  end
123
144
  end
124
145
 
125
146
  def in_transaction?
126
- @migration.connection.open_transactions > 0
147
+ connection.open_transactions > 0
127
148
  end
128
149
  end
129
150
  end
@@ -1,9 +1,9 @@
1
1
  module StrongMigrations
2
2
  module SchemaDumper
3
- def initialize(connection, *args, **options)
3
+ def initialize(connection, ...)
4
4
  return super unless StrongMigrations.alphabetize_schema
5
5
 
6
- super(WrappedConnection.new(connection), *args, **options)
6
+ super(WrappedConnection.new(connection), ...)
7
7
  end
8
8
  end
9
9
 
@@ -14,8 +14,19 @@ module StrongMigrations
14
14
  @connection = connection
15
15
  end
16
16
 
17
- def columns(*args, **options)
18
- @connection.columns(*args, **options).sort_by(&:name)
17
+ def columns(...)
18
+ @connection.columns(...).sort_by(&:name)
19
+ end
20
+
21
+ # forward private methods with send
22
+ # method_missing cannot tell how method was called
23
+ # this is not ideal, but other solutions have drawbacks
24
+ def send(name, ...)
25
+ if respond_to?(name, true)
26
+ super
27
+ else
28
+ @connection.send(name, ...)
29
+ end
19
30
  end
20
31
  end
21
32
  end
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "1.8.0"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -11,8 +11,8 @@ require_relative "strong_migrations/adapters/postgresql_adapter"
11
11
  require_relative "strong_migrations/checks"
12
12
  require_relative "strong_migrations/safe_methods"
13
13
  require_relative "strong_migrations/checker"
14
- require_relative "strong_migrations/database_tasks"
15
14
  require_relative "strong_migrations/migration"
15
+ require_relative "strong_migrations/migration_context"
16
16
  require_relative "strong_migrations/migrator"
17
17
  require_relative "strong_migrations/version"
18
18
 
@@ -29,7 +29,7 @@ module StrongMigrations
29
29
  :target_postgresql_version, :target_mysql_version, :target_mariadb_version,
30
30
  :enabled_checks, :lock_timeout, :statement_timeout, :check_down, :target_version,
31
31
  :safe_by_default, :target_sql_mode, :lock_timeout_retries, :lock_timeout_retry_delay,
32
- :alphabetize_schema
32
+ :alphabetize_schema, :skipped_databases, :remove_invalid_indexes
33
33
  attr_writer :lock_timeout_limit
34
34
  end
35
35
  self.auto_analyze = false
@@ -40,6 +40,8 @@ module StrongMigrations
40
40
  self.safe_by_default = false
41
41
  self.check_down = false
42
42
  self.alphabetize_schema = false
43
+ self.skipped_databases = []
44
+ self.remove_invalid_indexes = false
43
45
 
44
46
  # private
45
47
  def self.developer_env?
@@ -83,6 +85,10 @@ module StrongMigrations
83
85
  false
84
86
  end
85
87
  end
88
+
89
+ def self.skip_database(database)
90
+ self.skipped_databases << database
91
+ end
86
92
  end
87
93
 
88
94
  # load error messages
@@ -90,12 +96,9 @@ require_relative "strong_migrations/error_messages"
90
96
 
91
97
  ActiveSupport.on_load(:active_record) do
92
98
  ActiveRecord::Migration.prepend(StrongMigrations::Migration)
99
+ ActiveRecord::MigrationContext.prepend(StrongMigrations::MigrationContext)
93
100
  ActiveRecord::Migrator.prepend(StrongMigrations::Migrator)
94
101
 
95
- if defined?(ActiveRecord::Tasks::DatabaseTasks)
96
- ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(StrongMigrations::DatabaseTasks)
97
- end
98
-
99
102
  require_relative "strong_migrations/schema_dumper"
100
103
  ActiveRecord::SchemaDumper.prepend(StrongMigrations::SchemaDumper)
101
104
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  - Bob Remeika
9
9
  - David Waller
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2024-03-12 00:00:00.000000000 Z
12
+ date: 2025-04-03 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: activerecord
@@ -18,15 +17,14 @@ dependencies:
18
17
  requirements:
19
18
  - - ">="
20
19
  - !ruby/object:Gem::Version
21
- version: '5.2'
20
+ version: '7'
22
21
  type: :runtime
23
22
  prerelease: false
24
23
  version_requirements: !ruby/object:Gem::Requirement
25
24
  requirements:
26
25
  - - ">="
27
26
  - !ruby/object:Gem::Version
28
- version: '5.2'
29
- description:
27
+ version: '7'
30
28
  email:
31
29
  - andrew@ankane.org
32
30
  - bob.remeika@gmail.com
@@ -47,9 +45,9 @@ files:
47
45
  - lib/strong_migrations/adapters/postgresql_adapter.rb
48
46
  - lib/strong_migrations/checker.rb
49
47
  - lib/strong_migrations/checks.rb
50
- - lib/strong_migrations/database_tasks.rb
51
48
  - lib/strong_migrations/error_messages.rb
52
49
  - lib/strong_migrations/migration.rb
50
+ - lib/strong_migrations/migration_context.rb
53
51
  - lib/strong_migrations/migrator.rb
54
52
  - lib/strong_migrations/railtie.rb
55
53
  - lib/strong_migrations/safe_methods.rb
@@ -60,7 +58,6 @@ homepage: https://github.com/ankane/strong_migrations
60
58
  licenses:
61
59
  - MIT
62
60
  metadata: {}
63
- post_install_message:
64
61
  rdoc_options: []
65
62
  require_paths:
66
63
  - lib
@@ -68,15 +65,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
68
65
  requirements:
69
66
  - - ">="
70
67
  - !ruby/object:Gem::Version
71
- version: '2.6'
68
+ version: '3.1'
72
69
  required_rubygems_version: !ruby/object:Gem::Requirement
73
70
  requirements:
74
71
  - - ">="
75
72
  - !ruby/object:Gem::Version
76
73
  version: '0'
77
74
  requirements: []
78
- rubygems_version: 3.5.3
79
- signing_key:
75
+ rubygems_version: 3.6.2
80
76
  specification_version: 4
81
77
  summary: Catch unsafe migrations in development
82
78
  test_files: []