diff --git a/README.md b/README.md index dc388b57..c9255b72 100644 --- a/README.md +++ b/README.md @@ -77,12 +77,16 @@ tweak the schema in the new definition and run the `update_function` migration. ## I don't need this trigger or function anymore. Make it go away. -F(x) gives you `drop_trigger` and `drop_function` too: +F(x) gives you `drop_trigger` and `drop_function` too. These support the Rails 6.1 `if_exists: true` flags ```ruby def change drop_function :uppercase_users_name, revert_to_version: 2 end + +def up + drop_function :users_first_name, revert_to_version: 2, if_exists: true +end ``` ## What if I need to use a function as the default value of a column? diff --git a/lib/fx/adapters/postgres.rb b/lib/fx/adapters/postgres.rb index 8037becd..6ce56102 100644 --- a/lib/fx/adapters/postgres.rb +++ b/lib/fx/adapters/postgres.rb @@ -122,13 +122,14 @@ def update_trigger(name, on:, sql_definition:) # {Fx::Statements::Function#drop_function}. # # @param name The name of the function to drop + # @param if_exists If true, will not raise an error if the function does not exist # # @return [void] - def drop_function(name) + def drop_function(name, if_exists: false) if connection.support_drop_function_without_args - execute("DROP FUNCTION #{name};") + execute("DROP FUNCTION #{if_exists ? 'IF EXISTS ' : ' '}#{name};") else - execute("DROP FUNCTION #{name}();") + execute("DROP FUNCTION #{if_exists ? 'IF EXISTS ' : ' '}#{name}();") end end @@ -139,10 +140,11 @@ def drop_function(name) # # @param name The name of the trigger to drop # @param on The associated table for the trigger to drop + # @param if_exists If true, will not raise an error if the trigger does not exist # # @return [void] - def drop_trigger(name, on:) - execute("DROP TRIGGER #{name} ON #{on};") + def drop_trigger(name, on:, if_exists: false) + execute("DROP TRIGGER #{if_exists ? 'IF EXISTS ' : ' '}#{name} ON #{on};") end private diff --git a/lib/fx/statements.rb b/lib/fx/statements.rb index 665d0f4b..dfebc943 100644 --- a/lib/fx/statements.rb +++ b/lib/fx/statements.rb @@ -53,8 +53,12 @@ def create_function(name, options = {}) # @example Drop a function, rolling back to version 2 on rollback # drop_function(:uppercase_users_name, revert_to_version: 2) # + # @example Drop a function only if it exists + # drop_function(:uppercase_users_name, if_exists: true) + # def drop_function(name, options = {}) - Fx.database.drop_function(name) + if_exists = options.fetch(:if_exists, false) + Fx.database.drop_function(name, if_exists: if_exists) end # Update a database function. @@ -160,9 +164,13 @@ def create_trigger(name, options = {}) # @example Drop a trigger, rolling back to version 3 on rollback # drop_trigger(:log_inserts, on: :users, revert_to_version: 3) # + # @example Drop a trigger only if it exists + # drop_trigger(:log_inserts, on: :users, if_exists: true) + # def drop_trigger(name, options = {}) on = options.fetch(:on) - Fx.database.drop_trigger(name, on: on) + if_exists = options.fetch(:if_exists, false) + Fx.database.drop_trigger(name, on: on, if_exists: if_exists) end # Update a database trigger to a new version. diff --git a/spec/features/functions/migrations_spec.rb b/spec/features/functions/migrations_spec.rb index ce57992b..41b2b11d 100644 --- a/spec/features/functions/migrations_spec.rb +++ b/spec/features/functions/migrations_spec.rb @@ -37,6 +37,16 @@ def up expect { run_migration(migration, :up) }.not_to raise_error end + it "can run migrations that drop functions without raising an error" do + migration = Class.new(migration_class) do + def up + drop_function :test, if_exists: true # test has not been created + end + end + + expect { run_migration(migration, :up) }.not_to raise_error + end + it "can run migrations that updates functions" do connection.create_function(:test) diff --git a/spec/features/functions/revert_spec.rb b/spec/features/functions/revert_spec.rb index c326a1bd..c56a7076 100644 --- a/spec/features/functions/revert_spec.rb +++ b/spec/features/functions/revert_spec.rb @@ -47,6 +47,18 @@ def change ) end + it "can run reversible migrations for dropping functions with if_exists" do + connection.create_function(:test) + + good_migration = Class.new(migration_class) do + def change + drop_function :test, revert_to_version: 1, if_exists: true + end + end + + expect { run_migration(good_migration, [:up, :down]) }.not_to raise_error + end + it "can run reversible migrations for updating functions" do connection.create_function(:test) diff --git a/spec/fx/adapters/postgres_spec.rb b/spec/fx/adapters/postgres_spec.rb index f2ffccd9..f4df5b8a 100644 --- a/spec/fx/adapters/postgres_spec.rb +++ b/spec/fx/adapters/postgres_spec.rb @@ -91,6 +91,15 @@ expect(adapter.functions.map(&:name)).not_to include("test") end end + + context "when the function does not exist" do + it "successfully drops a function with if_exists = true" do + adapter = Fx::Adapters::Postgres.new + adapter.drop_function(:test, if_exists: true) + + expect(adapter.functions.map(&:name)).not_to include("test") + end + end end describe "#functions" do diff --git a/spec/fx/statements_spec.rb b/spec/fx/statements_spec.rb index 0d9c12f5..7d0c58a8 100644 --- a/spec/fx/statements_spec.rb +++ b/spec/fx/statements_spec.rb @@ -46,7 +46,15 @@ connection.drop_function(:test) - expect(database).to have_received(:drop_function).with(:test) + expect(database).to have_received(:drop_function).with(:test, if_exists: false) + end + + it "drops the function with if_exists" do + database = stubbed_database + + connection.drop_function(:test, if_exists: true) + + expect(database).to have_received(:drop_function).with(:test, if_exists: true) end end @@ -134,7 +142,16 @@ connection.drop_trigger(:test, on: :users) expect(database).to have_received(:drop_trigger) - .with(:test, on: :users) + .with(:test, on: :users, if_exists: false) + end + + it "drops the trigger with if_exists" do + database = stubbed_database + + connection.drop_trigger(:test, on: :users, if_exists: true) + + expect(database).to have_received(:drop_trigger) + .with(:test, on: :users, if_exists: true) end end