pg_ha_migrations 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc85b0812b8317e3f129a5ee82749513549298437822ae30df47c80c1150dfd3
4
- data.tar.gz: 88f379fb400d43d0fcf435646a6b102558d51909363a4daec4d519a865cac823
3
+ metadata.gz: 5b472247321efcc716bc1f7c4e497dcbe32058e4d2ce11d8f6d0d9fea7136755
4
+ data.tar.gz: 820bf96e3200deb2cffd30ba5534513600d05138f57f7812539cc8b33f96058a
5
5
  SHA512:
6
- metadata.gz: 15e3cc9067bf0befe6b348fc1743e74d1c1511c26d089907054e427560db4eee05f4f82fc68c29e56165ee08416c7dfb195e94859539999fd9fd1474d23cc0bd
7
- data.tar.gz: bcb14d8320a4aeb19617787076bcd01215c34e110a9e9d0562a24de87347958e86af2ee756a96d835e5f77844038a508bd76d9e623406ee5f8c3b3f4b49dfa5e
6
+ metadata.gz: b95318b6bf16ea1f5c9ec2514a6d6cccf912fc051caa9a954c58a038431d9afbbf01a23576f4ceaab1856922dc6e9aee5573743abb619f494c0f6023dcb217a9
7
+ data.tar.gz: 8bf47117312865cf57b51d200f3d131c266171a0f9899f39b8aa3f09aad50842555d93007a0b33a00873a7d67ec56a0f9b9027a028bf28338399939cd285ceda
data/.pryrc ADDED
@@ -0,0 +1,21 @@
1
+ if defined?(PryByebug)
2
+ Pry.commands.alias_command 'c', 'continue'
3
+ Pry.commands.alias_command 's', 'step'
4
+ Pry.commands.alias_command 'n', 'next'
5
+ Pry.commands.alias_command 'f', 'finish'
6
+ end
7
+
8
+ # https://github.com/pry/pry/issues/1275#issuecomment-131969510
9
+ # Prevent issue where text input does not display on screen in container after typing Ctrl-C in a pry repl
10
+ at_exit do
11
+ exit!(1)
12
+ end
13
+
14
+ trap('INT') do
15
+ begin
16
+ Pry.run_command "continue", :show_output => true, :target => Pry.current
17
+ rescue
18
+ exit
19
+ end
20
+ end
21
+ # End pry Ctrl-C workaround
data/.travis.yml CHANGED
@@ -6,4 +6,6 @@ services:
6
6
  - postgresql
7
7
  addons:
8
8
  postgresql: "9.6"
9
- before_install: gem install bundler -v 1.15.4
9
+ before_install:
10
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
11
+ - gem install bundler -v 1.15.4
data/README.md CHANGED
@@ -48,11 +48,25 @@ and never use `def change`. We believe that this is the only safe approach in pr
48
48
 
49
49
  ### Migrations
50
50
 
51
- In general, existing migrations are prefixed with `unsafe_` and safer alternatives are provided prefixed with `safe_`.
51
+ There are two major classes of concerns we try to handle in the API:
52
52
 
53
- Migrations prefixed with `unsafe_` will warn when invoked. The API is designed to be explicit yet remain flexible. There may be situations where invoking the unsafe migration is preferred.
53
+ - Database safety (e.g., long-held locks)
54
+ - Application safety (e.g., dropping columns the app uses)
54
55
 
55
- Migrations prefixed with `safe_` prefer concurrent operations where available, set low lock timeouts where appropriate, and decompose operations into multiple safe steps.
56
+ We rename migration methods with prefixes denoting their safety level:
57
+
58
+ - `safe_*`: These methods check for both application and database safety concerns prefer concurrent operations where available, set low lock timeouts where appropriate, and decompose operations into multiple safe steps.
59
+ - `unsafe_*`: These methods are generally a direct dispatch to the native ActiveRecord migration method.
60
+
61
+ Calling the original migration methods without a prefix will raise an error.
62
+
63
+ The API is designed to be explicit yet remain flexible. There may be situations where invoking the `unsafe_*` method is preferred (or the only option available for definitionally unsafe operations).
64
+
65
+ While `unsafe_*` methods were historically (through 1.0) pure wrappers for invoking the native ActiveRecord migration method, there is a class of problems that we can't handle easily without breaking that design rule a bit. For example, dropping a column is unsafe from an application perspective, so we make the application safety concerns explicit by using an `unsafe_` prefix. Using `unsafe_remove_column` calls out the need to audit the application to confirm the migration won't break the application. Because there are no safe alternatives we don't define a `safe_remove_column` analogue. However there are still conditions we'd like to assert before dropping a column. For example, dropping an unused column that's used in one or more indexes may be safe from an application perspective, but the cascading drop of the index won't use a `CONCURRENT` operation to drop the dependent indexes and is therefore unsafe from a database perspective.
66
+
67
+ When `unsafe_*` migration methods support checks of this type you can bypass the checks by passing an `:allow_dependent_objects` key in the method's `options` hash containing an array of dependent object types you'd like to allow. Until 2.0 none of these checks will run by default, but you can opt-in by setting `config.check_for_dependent_objects = true` [in your configuration initializer](#configuration).
68
+
69
+ Similarly we believe the `force: true` option to ActiveRecord's `create_table` method is always unsafe, and therefore we disallow it even when calling `unsafe_create_table`. This option won't be enabled by default until 2.0, but you can opt-in by setting `config.allow_force_create_table = false` [in your configuration initializer](#configuration).
56
70
 
57
71
  [Running multiple DDL statements inside a transaction acquires exclusive locks on all of the modified objects](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#cc22). For that reason, this gem [disables DDL transactions](./lib/pg_ha_migrations.rb:8) by default. You can change this by resetting `ActiveRecord::Migration.disable_ddl_transaction` in your application.
58
72
 
@@ -195,6 +209,20 @@ Set maintenance work mem.
195
209
  safe_set_maintenance_work_mem_gb 1
196
210
  ```
197
211
 
212
+ ### Configuration
213
+
214
+ The gem can be configured in an initializer.
215
+
216
+ ```ruby
217
+ PgHaMigrations.configure do |config|
218
+ # ...
219
+ end
220
+ ```
221
+
222
+ #### Available options
223
+
224
+ - `disable_default_migration_methods`: If true, the default implementations of DDL changes in `ActiveRecord::Migration` and the PostgreSQL adapter will be overridden by implementations that raise a `PgHaMigrations::UnsafeMigrationError`. Default: `true`
225
+ - `check_for_dependent_objects`: If true, some `unsafe_*` migration methods will raise a `PgHaMigrations::UnsafeMigrationError` if any dependent objects exist. Default: `false`
198
226
 
199
227
  ### Rake Tasks
200
228
 
@@ -220,6 +248,8 @@ Rake::Task["pg_ha_migrations:check_blocking_database_transactions"].enhance ["db
220
248
 
221
249
  After checking out the repo, run `bin/setup` to install dependencies and start a postgres docker container. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
222
250
 
251
+ Running tests will automatically create a test database in the locally running Postgres server. You can find the connection parameters in `spec/spec_helper.rb`, but setting the environment variables `PGHOST`, `PGPORT`, `PGUSER`, and `PGPASSWORD` will override the defaults.
252
+
223
253
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
224
254
 
225
255
  ## Contributing
@@ -29,9 +29,7 @@ module PgHaMigrations
29
29
  migrations in this deploy that will attempt to create additional
30
30
  concurrent indexes on the same physical database (even if the table
31
31
  being indexes is on another dimension) those migrations will not be
32
- able to complete until the in-progress index creations finish.
33
-
34
- For more information, see #service-db in Slack.\n
32
+ able to complete until the in-progress index creations finish.\n
35
33
  eos
36
34
  report << "\n" # Blank line intentional
37
35
  end
@@ -0,0 +1,61 @@
1
+ module PgHaMigrations::DependentObjectsChecks
2
+ ALLOWED_TYPES_OPTIONS = [:indexes].freeze
3
+
4
+ ObjectDependency = Struct.new(:owner_type, :owner_name, :dependent_type, :dependent_name) do
5
+ def error_text
6
+ "#{dependent_type} '#{dependent_name}' depends on #{owner_type} '#{owner_name}'"
7
+ end
8
+ end
9
+
10
+ def dependent_objects_for_migration_method(method_name, arguments:)
11
+ allowed_types = arguments.last.is_a?(Hash) ? arguments.last[:allow_dependent_objects] || [] : []
12
+
13
+ if (invalid_allowed_types = allowed_types - ALLOWED_TYPES_OPTIONS).present?
14
+ raise ArgumentError, "Received invalid entries in allow_dependent_objects: #{invalid_allowed_types.inspect}"
15
+ end
16
+
17
+ dependent_objects = []
18
+
19
+ case method_name
20
+ when :remove_column
21
+ table_name = arguments[0]
22
+ column_name = arguments[1]
23
+
24
+ unless allowed_types.include?(:indexes)
25
+ # https://www.postgresql.org/docs/current/catalog-pg-depend.html
26
+ # deptype:
27
+ # - 'a' (DEPENDENCY_AUTO): the dependent object should be automatically
28
+ # dropped if the referenced object is dropped
29
+ indexes = ActiveRecord::Base.structs_from_sql(ObjectDependency, <<~SQL)
30
+ SELECT
31
+ 'column' AS owner_type,
32
+ ref_attr.attname AS owner_name,
33
+ 'index' AS dependent_type,
34
+ dep_class.relname AS dependent_name
35
+ FROM pg_catalog.pg_depend
36
+ JOIN pg_catalog.pg_class ref_class ON ref_class.oid = pg_depend.refobjid
37
+ JOIN pg_catalog.pg_attribute ref_attr ON ref_attr.attrelid = ref_class.oid
38
+ AND ref_attr.attnum = pg_depend.refobjsubid
39
+ JOIN pg_catalog.pg_class dep_class ON dep_class.oid = pg_depend.objid
40
+ WHERE pg_depend.deptype = 'a'
41
+ AND pg_depend.refclassid = 'pg_class'::regclass
42
+ -- This doesn't currently handle table names that are duplicative across schemas.
43
+ AND ref_class.relname = '#{PG::Connection.escape_string(table_name.to_s)}'
44
+ AND ref_attr.attname = '#{PG::Connection.escape_string(column_name.to_s)}'
45
+ AND pg_depend.classid = 'pg_class'::regclass
46
+ SQL
47
+ dependent_objects.concat(indexes)
48
+ end
49
+ end
50
+ end
51
+
52
+ def disallow_migration_method_if_dependent_objects!(method_name, arguments:)
53
+ dependent_objects = dependent_objects_for_migration_method(method_name, arguments: arguments)
54
+
55
+ if dependent_objects.present?
56
+ raise PgHaMigrations::UnsafeMigrationError, dependent_objects.map(&:error_text).join("; ")
57
+ end
58
+ end
59
+ end
60
+
61
+ ActiveRecord::Migration.prepend(PgHaMigrations::DependentObjectsChecks)
@@ -1,5 +1,9 @@
1
1
  module PgHaMigrations::SafeStatements
2
2
  def safe_create_table(table, options={}, &block)
3
+ if options[:force]
4
+ raise PgHaMigrations::UnsafeMigrationError.new(":force is NOT SAFE! Explicitly call unsafe_drop_table first if you want to recreate an existing table")
5
+ end
6
+
3
7
  unsafe_create_table(table, options, &block)
4
8
  end
5
9
 
@@ -38,20 +42,17 @@ module PgHaMigrations::SafeStatements
38
42
  end
39
43
  end
40
44
 
41
- def safe_change_column_default(table, column, default_value)
42
- escaped_value = case default_value
43
- when Proc
44
- PG::Connection.escape_string(default_value.call.to_s)
45
- else
46
- "'#{PG::Connection.escape_string(default_value.to_s)}'"
45
+ def safe_change_column_default(table_name, column_name, default_value)
46
+ column = connection.send(:column_for, table_name, column_name)
47
+
48
+ if default_value.present? &&
49
+ !default_value.is_a?(Proc) &&
50
+ quote_default_expression(default_value, column) == "NULL"
51
+ raise PgHaMigrations::InvalidMigrationError, "Requested new default value of <#{default_value}>, but that casts to NULL for the type <#{column.type}>. Did you mean to you mean to use a Proc instead?"
47
52
  end
48
53
 
49
- safely_acquire_lock_for_table(table) do
50
- unsafe_execute <<-SQL.strip_heredoc
51
- ALTER TABLE #{PG::Connection.quote_ident(table.to_s)}
52
- ALTER COLUMN #{PG::Connection.quote_ident(column.to_s)}
53
- SET DEFAULT #{escaped_value}
54
- SQL
54
+ safely_acquire_lock_for_table(table_name) do
55
+ unsafe_change_column_default(table_name, column_name, default_value)
55
56
  end
56
57
  end
57
58
 
@@ -1,114 +1,110 @@
1
1
  module PgHaMigrations::UnsafeStatements
2
- def self.delegate_unsafe_method_to_migration_base_class(method_name)
3
- define_method("unsafe_#{method_name}") do |*args, &block|
4
- # Dispatching here is a bit complicated: we need to execute the method
5
- # belonging to the first member of the inheritance chain (besides
6
- # UnsafeStatements). If don't find the method in the inheritance chain,
7
- # we need to rely on the ActiveRecord::Migration#method_missing
8
- # implementation since much of ActiveRecord::Migration's functionality
9
- # is not implemented in real methods but rather by proxying.
10
- #
11
- # For example, ActiveRecord::Migration doesn't define #create_table.
12
- # Instead ActiveRecord::Migration#method_missing proxies the method
13
- # to the connection. However some migration compatibility version
14
- # subclasses _do_ explicitly define #create_table, so we can't rely
15
- # on only one way of finding the proper dispatch target.
16
-
17
- # Exclude our `raise` guard implementations.
18
- ancestors_without_unsafe_statements = self.class.ancestors - [PgHaMigrations::UnsafeStatements]
19
-
20
- delegate_method = self.method(method_name)
21
- candidate_method = delegate_method
22
-
23
- # Find the first usable method in the ancestor chain
24
- # or stop looking if there are no more possible
25
- # implementations.
26
- until candidate_method.nil? || ancestors_without_unsafe_statements.include?(candidate_method.owner)
27
- candidate_method = candidate_method.super_method
2
+ def self.disable_or_delegate_default_method(method_name, error_message, allow_reentry_from_compatibility_module: false)
3
+ define_method(method_name) do |*args, &block|
4
+ if PgHaMigrations.config.check_for_dependent_objects
5
+ disallow_migration_method_if_dependent_objects!(method_name, arguments: args)
28
6
  end
29
7
 
30
- if candidate_method
31
- delegate_method = candidate_method
8
+ if PgHaMigrations.config.disable_default_migration_methods
9
+ # Most migration methods are only ever called by a migration and
10
+ # therefore aren't re-entrant or callable from another migration
11
+ # method, but `execute` is called directly by at least one of the
12
+ # implementations in `ActiveRecord::Migration::Compatibility` so
13
+ # we have to explicitly handle that case by allowing execution of
14
+ # the original implementation by its original name.
15
+ unless allow_reentry_from_compatibility_module && caller[0] =~ /lib\/active_record\/migration\/compatibility.rb/
16
+ raise PgHaMigrations::UnsafeMigrationError, error_message
17
+ end
32
18
  end
33
19
 
34
- # If we failed to find a concrete implementation from the
35
- # inheritance chain, use ActiveRecord::Migrations# method_missing
36
- # otherwise use the method from the inheritance chain.
37
- if delegate_method.owner == PgHaMigrations::UnsafeStatements
38
- method_missing(method_name, *args, &block)
39
- else
40
- delegate_method.call(*args, &block)
41
- end
20
+ execute_ancestor_statement(method_name, *args, &block)
42
21
  end
43
22
  end
44
23
 
45
- delegate_unsafe_method_to_migration_base_class :create_table
46
- def create_table(name, options={})
47
- raise PgHaMigrations::UnsafeMigrationError.new(":create_table is NOT SAFE! Use safe_create_table instead")
48
- end
24
+ def self.delegate_unsafe_method_to_migration_base_class(method_name)
25
+ define_method("unsafe_#{method_name}") do |*args, &block|
26
+ if PgHaMigrations.config.check_for_dependent_objects
27
+ disallow_migration_method_if_dependent_objects!(method_name, arguments: args)
28
+ end
49
29
 
50
- delegate_unsafe_method_to_migration_base_class :add_column
51
- def add_column(table, column, type, options={})
52
- raise PgHaMigrations::UnsafeMigrationError.new(":add_column is NOT SAFE! Use safe_add_column instead")
30
+ execute_ancestor_statement(method_name, *args, &block)
31
+ end
53
32
  end
54
33
 
34
+ delegate_unsafe_method_to_migration_base_class :add_column
55
35
  delegate_unsafe_method_to_migration_base_class :change_table
56
- def change_table(name, options={})
57
- raise PgHaMigrations::UnsafeMigrationError.new(":change_table is NOT SAFE! Use a combination of safe and explicit unsafe migration methods instead")
58
- end
59
-
60
36
  delegate_unsafe_method_to_migration_base_class :drop_table
61
- def drop_table(name)
62
- raise PgHaMigrations::UnsafeMigrationError.new(":drop_table is NOT SAFE! Explicitly call :unsafe_drop_table to proceed")
63
- end
64
-
65
37
  delegate_unsafe_method_to_migration_base_class :rename_table
66
- def rename_table(old_name, new_name)
67
- raise PgHaMigrations::UnsafeMigrationError.new(":rename_table is NOT SAFE! Explicitly call :unsafe_rename_table to proceed")
68
- end
69
-
70
38
  delegate_unsafe_method_to_migration_base_class :rename_column
71
- def rename_column(table_name, column_name, new_column_name)
72
- raise PgHaMigrations::UnsafeMigrationError.new(":rename_column is NOT SAFE! Explicitly call :unsafe_rename_column to proceed")
73
- end
74
-
75
39
  delegate_unsafe_method_to_migration_base_class :change_column
76
- def change_column(table_name, column_name, type, options={})
77
- raise PgHaMigrations::UnsafeMigrationError.new(":change_column is NOT SAFE! Use a combination of safe and explicit unsafe migration methods instead")
78
- end
79
-
80
- def change_column_null(table_name, column_name, null, default = nil)
81
- raise PgHaMigrations::UnsafeMigrationError.new(<<-EOS.strip_heredoc)
82
- :change_column_null is NOT (guaranteed to be) SAFE! Either use :safe_make_column_nullable or explicitly call :unsafe_make_column_not_nullable to proceed
83
- EOS
84
- end
85
-
40
+ delegate_unsafe_method_to_migration_base_class :change_column_default
86
41
  delegate_unsafe_method_to_migration_base_class :remove_column
87
- def remove_column(table_name, column_name, type, options={})
88
- raise PgHaMigrations::UnsafeMigrationError.new(":remove_column is NOT SAFE! Explicitly call :unsafe_remove_column to proceed")
89
- end
90
-
91
42
  delegate_unsafe_method_to_migration_base_class :add_index
92
- def add_index(table_name, column_names, options={})
93
- raise PgHaMigrations::UnsafeMigrationError.new(":add_index is NOT SAFE! Use safe_add_concurrent_index instead")
94
- end
95
-
96
43
  delegate_unsafe_method_to_migration_base_class :execute
97
- def execute(sql, name = nil)
98
- if caller[0] =~ /lib\/active_record\/migration\/compatibility.rb/
99
- super
100
- else
101
- raise PgHaMigrations::UnsafeMigrationError.new(":execute is NOT SAFE! Explicitly call :unsafe_execute to proceed")
44
+ delegate_unsafe_method_to_migration_base_class :remove_index
45
+ delegate_unsafe_method_to_migration_base_class :add_foreign_key
46
+
47
+ disable_or_delegate_default_method :create_table, ":create_table is NOT SAFE! Use safe_create_table instead"
48
+ disable_or_delegate_default_method :add_column, ":add_column is NOT SAFE! Use safe_add_column instead"
49
+ disable_or_delegate_default_method :change_table, ":change_table is NOT SAFE! Use a combination of safe and explicit unsafe migration methods instead"
50
+ disable_or_delegate_default_method :drop_table, ":drop_table is NOT SAFE! Explicitly call :unsafe_drop_table to proceed"
51
+ disable_or_delegate_default_method :rename_table, ":rename_table is NOT SAFE! Explicitly call :unsafe_rename_table to proceed"
52
+ disable_or_delegate_default_method :rename_column, ":rename_column is NOT SAFE! Explicitly call :unsafe_rename_column to proceed"
53
+ disable_or_delegate_default_method :change_column, ":change_column is NOT SAFE! Use a combination of safe and explicit unsafe migration methods instead"
54
+ disable_or_delegate_default_method :change_column_default, ":change_column_default is NOT SAFE! Use safe_change_column_default instead"
55
+ disable_or_delegate_default_method :change_column_null, ":change_column_null is NOT (guaranteed to be) SAFE! Either use :safe_make_column_nullable or explicitly call :unsafe_make_column_not_nullable to proceed"
56
+ disable_or_delegate_default_method :remove_column, ":remove_column is NOT SAFE! Explicitly call :unsafe_remove_column to proceed"
57
+ disable_or_delegate_default_method :add_index, ":add_index is NOT SAFE! Use safe_add_concurrent_index instead"
58
+ disable_or_delegate_default_method :execute, ":execute is NOT SAFE! Explicitly call :unsafe_execute to proceed", allow_reentry_from_compatibility_module: true
59
+ disable_or_delegate_default_method :remove_index, ":remove_index is NOT SAFE! Use safe_remove_concurrent_index instead for Postgres 9.6 databases; Explicitly call :unsafe_remove_index to proceed on Postgres 9.1"
60
+ disable_or_delegate_default_method :add_foreign_key, ":add_foreign_key is NOT SAFE! Explicitly call :unsafe_add_foreign_key"
61
+
62
+ def unsafe_create_table(table, options={}, &block)
63
+ if options[:force] && !PgHaMigrations.config.allow_force_create_table
64
+ raise PgHaMigrations::UnsafeMigrationError.new(":force is NOT SAFE! Explicitly call unsafe_drop_table first if you want to recreate an existing table")
102
65
  end
103
- end
104
66
 
105
- delegate_unsafe_method_to_migration_base_class :remove_index
106
- def remove_index(table_name, options={})
107
- raise PgHaMigrations::UnsafeMigrationError.new(":remove_index is NOT SAFE! Use safe_remove_concurrent_index instead for Postgres 9.6 databases; Explicitly call :unsafe_remove_index to proceed on Postgres 9.1")
67
+ execute_ancestor_statement(:create_table, table, options, &block)
108
68
  end
109
69
 
110
- delegate_unsafe_method_to_migration_base_class :add_foreign_key
111
- def add_foreign_key(from_table, to_table, options)
112
- raise PgHaMigrations::UnsafeMigrationError.new(":add_foreign_key is NOT SAFE! Explicitly call :unsafe_add_foreign_key only if you have guidance from a migration reviewer in #service-app-db.")
70
+ def execute_ancestor_statement(method_name, *args, &block)
71
+ # Dispatching here is a bit complicated: we need to execute the method
72
+ # belonging to the first member of the inheritance chain (besides
73
+ # UnsafeStatements). If don't find the method in the inheritance chain,
74
+ # we need to rely on the ActiveRecord::Migration#method_missing
75
+ # implementation since much of ActiveRecord::Migration's functionality
76
+ # is not implemented in real methods but rather by proxying.
77
+ #
78
+ # For example, ActiveRecord::Migration doesn't define #create_table.
79
+ # Instead ActiveRecord::Migration#method_missing proxies the method
80
+ # to the connection. However some migration compatibility version
81
+ # subclasses _do_ explicitly define #create_table, so we can't rely
82
+ # on only one way of finding the proper dispatch target.
83
+
84
+ # Exclude our `raise` guard implementations.
85
+ ancestors_without_unsafe_statements = self.class.ancestors - [PgHaMigrations::UnsafeStatements]
86
+
87
+ delegate_method = self.method(method_name)
88
+ candidate_method = delegate_method
89
+
90
+ # Find the first usable method in the ancestor chain
91
+ # or stop looking if there are no more possible
92
+ # implementations.
93
+ until candidate_method.nil? || ancestors_without_unsafe_statements.include?(candidate_method.owner)
94
+ candidate_method = candidate_method.super_method
95
+ end
96
+
97
+ if candidate_method
98
+ delegate_method = candidate_method
99
+ end
100
+
101
+ # If we failed to find a concrete implementation from the
102
+ # inheritance chain, use ActiveRecord::Migrations# method_missing
103
+ # otherwise use the method from the inheritance chain.
104
+ if delegate_method.owner == PgHaMigrations::UnsafeStatements
105
+ method_missing(method_name, *args, &block)
106
+ else
107
+ delegate_method.call(*args, &block)
108
+ end
113
109
  end
114
110
  end
@@ -1,3 +1,3 @@
1
1
  module PgHaMigrations
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -5,6 +5,24 @@ require "active_record/migration"
5
5
  require "relation_to_struct"
6
6
 
7
7
  module PgHaMigrations
8
+ Config = Struct.new(
9
+ :disable_default_migration_methods,
10
+ :check_for_dependent_objects,
11
+ :allow_force_create_table
12
+ )
13
+
14
+ def self.config
15
+ @config ||= Config.new(
16
+ true,
17
+ false,
18
+ true
19
+ )
20
+ end
21
+
22
+ def self.configure
23
+ yield config
24
+ end
25
+
8
26
  LOCK_TIMEOUT_SECONDS = 5
9
27
  LOCK_FAILURE_RETRY_DELAY_MULTLIPLIER = 5
10
28
 
@@ -25,6 +43,7 @@ require "pg_ha_migrations/blocking_database_transactions"
25
43
  require "pg_ha_migrations/blocking_database_transactions_reporter"
26
44
  require "pg_ha_migrations/unsafe_statements"
27
45
  require "pg_ha_migrations/safe_statements"
46
+ require "pg_ha_migrations/dependent_objects_checks"
28
47
  require "pg_ha_migrations/allowed_versions"
29
48
  require "pg_ha_migrations/railtie"
30
49
  require "pg_ha_migrations/hacks/disable_ddl_transaction"
@@ -34,7 +34,8 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "rspec", "~> 3.0"
35
35
  spec.add_development_dependency "pg"
36
36
  spec.add_development_dependency "db-query-matchers", "~> 0.9.0"
37
-
37
+ spec.add_development_dependency 'pry'
38
+ spec.add_development_dependency 'pry-byebug'
38
39
 
39
40
  spec.add_dependency "rails", ">= 5.0", "< 5.3"
40
41
  spec.add_dependency "relation_to_struct"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_ha_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - celeen
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: exe
16
16
  cert_chain: []
17
- date: 2019-03-04 00:00:00.000000000 Z
17
+ date: 2019-09-05 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: bundler
@@ -86,6 +86,34 @@ dependencies:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: 0.9.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: pry-byebug
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
89
117
  - !ruby/object:Gem::Dependency
90
118
  name: rails
91
119
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +157,7 @@ extensions: []
129
157
  extra_rdoc_files: []
130
158
  files:
131
159
  - ".gitignore"
160
+ - ".pryrc"
132
161
  - ".rspec"
133
162
  - ".ruby-version"
134
163
  - ".travis.yml"
@@ -143,6 +172,7 @@ files:
143
172
  - lib/pg_ha_migrations/allowed_versions.rb
144
173
  - lib/pg_ha_migrations/blocking_database_transactions.rb
145
174
  - lib/pg_ha_migrations/blocking_database_transactions_reporter.rb
175
+ - lib/pg_ha_migrations/dependent_objects_checks.rb
146
176
  - lib/pg_ha_migrations/hacks/disable_ddl_transaction.rb
147
177
  - lib/pg_ha_migrations/railtie.rb
148
178
  - lib/pg_ha_migrations/safe_statements.rb
@@ -169,8 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
199
  - !ruby/object:Gem::Version
170
200
  version: '0'
171
201
  requirements: []
172
- rubyforge_project:
173
- rubygems_version: 2.7.8
202
+ rubygems_version: 3.0.3
174
203
  signing_key:
175
204
  specification_version: 4
176
205
  summary: Enforces DDL/migration safety in Ruby on Rails project with an emphasis on