Skip to content

Commit a0c1d33

Browse files
authored
Merge pull request rails#45735 from ghiculescu/drop-enum
Add `drop_enum` command for Postgres
2 parents c558d98 + bd0fdc8 commit a0c1d33

File tree

8 files changed

+88
-17
lines changed

8 files changed

+88
-17
lines changed

activerecord/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* Add `drop_enum` migration command for PostgreSQL
2+
3+
This does the inverse of `create_enum`. Before dropping an enum, ensure you have
4+
dropped columns that depend on it.
5+
6+
*Alex Ghiculescu*
7+
18
* Adds support for `if_exists` option when removing a check constraint.
29

310
The `remove_check_constraint` method now accepts an `if_exists` option. If set

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,10 @@ def enable_extension(name, **)
535535
def create_enum(*) # :nodoc:
536536
end
537537

538+
# This is meant to be implemented by the adapters that support custom enum types
539+
def drop_enum(*) # :nodoc:
540+
end
541+
538542
def advisory_locks_enabled? # :nodoc:
539543
supports_advisory_locks? && @advisory_locks_enabled
540544
end

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,17 @@ def create_enum(name, values)
486486
exec_query(query)
487487
end
488488

489+
# Drops an enum type.
490+
# If the `if_exists: true` option is provided, the enum is only dropped if it exists.
491+
# Otherwise, if the enum doesn't exist, an error is raised.
492+
def drop_enum(name, *args)
493+
options = args.extract_options!
494+
query = <<~SQL
495+
DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
496+
SQL
497+
exec_query(query)
498+
end
499+
489500
# Returns the configured supported identifier length supported by PostgreSQL
490501
def max_identifier_length
491502
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i

activerecord/lib/active_record/migration/command_recorder.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class CommandRecorder
4545
:add_foreign_key, :remove_foreign_key,
4646
:change_column_comment, :change_table_comment,
4747
:add_check_constraint, :remove_check_constraint,
48-
:add_exclusion_constraint, :remove_exclusion_constraint
48+
:add_exclusion_constraint, :remove_exclusion_constraint,
49+
:create_enum, :drop_enum
4950
]
5051
include JoinTable
5152

@@ -144,7 +145,8 @@ module StraightReversions # :nodoc:
144145
add_foreign_key: :remove_foreign_key,
145146
add_check_constraint: :remove_check_constraint,
146147
add_exclusion_constraint: :remove_exclusion_constraint,
147-
enable_extension: :disable_extension
148+
enable_extension: :disable_extension,
149+
create_enum: :drop_enum
148150
}.each do |cmd, inv|
149151
[[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
150152
class_eval <<-EOV, __FILE__, __LINE__ + 1

activerecord/test/cases/adapters/postgresql/enum_test.rb

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ def setup
3030
end
3131

3232
teardown do
33+
reset_connection
3334
@connection.drop_table "postgresql_enums", if_exists: true
34-
@connection.execute "DROP TYPE IF EXISTS mood"
35+
@connection.drop_enum "mood", if_exists: true
3536
reset_connection
3637
end
3738

@@ -126,6 +127,22 @@ def test_schema_load
126127
$stdout = original
127128
end
128129

130+
def test_drop_enum
131+
@connection.create_enum :unused, []
132+
133+
assert_nothing_raised do
134+
@connection.drop_enum "unused"
135+
end
136+
137+
assert_nothing_raised do
138+
@connection.drop_enum "unused", if_exists: true
139+
end
140+
141+
assert_raises ActiveRecord::StatementInvalid do
142+
@connection.drop_enum "unused"
143+
end
144+
end
145+
129146
def test_works_with_activerecord_enum
130147
model = PostgresqlEnum.create!
131148
model.current_mood_okay!
@@ -145,13 +162,18 @@ def test_enum_type_scoped_to_schemas
145162
@connection.create_schema("test_schema")
146163
@connection.schema_search_path = "test_schema"
147164
@connection.schema_cache.clear!
148-
@connection.create_enum("mood", ["sad", "ok", "happy"])
165+
@connection.create_enum("mood_in_other_schema", ["sad", "ok", "happy"])
149166

150167
assert_nothing_raised do
151-
@connection.create_table("postgresql_enums") do |t|
152-
t.column :current_mood, :mood, default: "happy", null: false
168+
@connection.create_table("postgresql_enums_in_other_schema") do |t|
169+
t.column :current_mood, :mood_in_other_schema, default: "happy", null: false
153170
end
154171
end
172+
173+
assert_nothing_raised do
174+
@connection.drop_table("postgresql_enums_in_other_schema")
175+
@connection.drop_enum("mood_in_other_schema")
176+
end
155177
ensure
156178
@connection.drop_schema("test_schema")
157179
@connection.schema_search_path = old_search_path
@@ -160,13 +182,18 @@ def test_enum_type_scoped_to_schemas
160182

161183
def test_enum_type_explicit_schema
162184
@connection.create_schema("test_schema")
163-
@connection.create_enum("test_schema.mood", ["sad", "ok", "happy"])
185+
@connection.create_enum("test_schema.mood_in_other_schema", ["sad", "ok", "happy"])
164186

165-
@connection.create_table("test_schema.postgresql_enums") do |t|
166-
t.column :current_mood, "test_schema.mood"
187+
@connection.create_table("test_schema.postgresql_enums_in_other_schema") do |t|
188+
t.column :current_mood, "test_schema.mood_in_other_schema"
167189
end
168190

169-
assert @connection.table_exists?("test_schema.postgresql_enums")
191+
assert @connection.table_exists?("test_schema.postgresql_enums_in_other_schema")
192+
193+
assert_nothing_raised do
194+
@connection.drop_table("test_schema.postgresql_enums_in_other_schema")
195+
@connection.drop_enum("test_schema.mood_in_other_schema")
196+
end
170197
ensure
171198
@connection.drop_schema("test_schema", if_exists: true)
172199
end

activerecord/test/cases/adapters/postgresql/invertible_migration_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ def change
2020
end
2121
end
2222

23+
class CreateEnumMigration < SilentMigration
24+
def change
25+
create_enum :color, ["blue", "green"]
26+
create_table :enums do |t|
27+
t.enum :best_color, enum_type: "color", default: "blue", null: false
28+
end
29+
end
30+
end
31+
2332
self.use_transactional_tests = false
2433

2534
setup do
@@ -28,6 +37,7 @@ def change
2837

2938
teardown do
3039
@connection.drop_table "settings", if_exists: true
40+
@connection.drop_table "enums", if_exists: true
3141
end
3242

3343
def test_migrate_revert_add_index_with_expression
@@ -43,4 +53,16 @@ def test_migrate_revert_add_index_with_expression
4353
assert_not @connection.index_exists?(:settings, nil, name: "index_settings_data_foo"),
4454
"index index_settings_data_foo should not exist"
4555
end
56+
57+
def test_revert_create_enum
58+
CreateEnumMigration.new.migrate(:up)
59+
60+
assert @connection.column_exists?(:enums, :best_color, sql_type: "color", default: "blue", null: false)
61+
assert_equal [["color", "blue,green"]], @connection.enum_types
62+
63+
CreateEnumMigration.new.migrate(:down)
64+
65+
assert_not @connection.table_exists?(:enums)
66+
assert_equal [], @connection.enum_types
67+
end
4668
end

activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,12 @@ def test_raise_error_when_cannot_translate_exception
396396
end
397397

398398
def test_reload_type_map_for_newly_defined_types
399-
@connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
399+
@connection.create_enum "feeling", ["good", "bad"]
400400
result = @connection.select_all "SELECT 'good'::feeling"
401401
assert_instance_of(PostgreSQLAdapter::OID::Enum,
402402
result.column_types["feeling"])
403403
ensure
404-
@connection.execute "DROP TYPE IF EXISTS feeling"
404+
@connection.drop_enum "feeling", if_exists: true
405405
reset_connection
406406
end
407407

guides/source/active_record_postgresql.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,12 @@ def up
261261
end
262262
end
263263

264-
# There's no built in support for dropping enums, but you can do it manually.
265-
# You should first drop any table that depends on them.
264+
# The above migration is reversible (using #change), but you can
265+
# also define a #down method:
266266
def down
267267
drop_table :articles
268268

269-
execute <<-SQL
270-
DROP TYPE article_status;
271-
SQL
269+
drop_enum :article_status
272270
end
273271
```
274272

0 commit comments

Comments
 (0)