Skip to content

Commit 25f97a6

Browse files
eileencodeseudaimoniousAdityaBhutani
committed
Add support for if_exists option when removing a check constraint
The `remove_check_constraint` method now accepts an `if_exists` option. If set to true an error won't be raised if the check constraint doesn't exist. This commit is a combination of PR rails#45726 and rails#45718 with some additional changes to improve wording, testing, and implementation. Usage: ```ruby remove_check_constraint :products, name: "price_check", if_exists: true ``` Fixes rails#45634 Co-authored-by: Margaret Parsa <[email protected]> Co-authored-by: Aditya Bhutani <[email protected]>
1 parent 1288289 commit 25f97a6

File tree

5 files changed

+49
-1
lines changed

5 files changed

+49
-1
lines changed

activerecord/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* Adds support for `if_exists` option when removing a check constraint.
2+
3+
The `remove_check_constraint` method now accepts an `if_exists` option. If set
4+
to true an error won't be raised if the check constraint doesn't exist.
5+
6+
*Margaret Parsa* and *Aditya Bhutani*
7+
18
* `find_or_create_by` now try to find a second time if it hits a unicity constraint.
29

310
`find_or_create_by` always has been inherently racy, either creating multiple

activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,17 @@ def remove_check_constraint(*args, **options)
889889
@base.remove_check_constraint(name, *args, **options)
890890
end
891891

892+
# Checks if a check_constraint exists on a table.
893+
#
894+
# unless t.check_constraint_exists?(name: "price_check")
895+
# t.check_constraint("price > 0", name: "price_check")
896+
# end
897+
#
898+
# See {connection.check_constraint_exists?}[rdoc-ref:SchemaStatements#check_constraint_exists?]
899+
def check_constraint_exists?(*args, **options)
900+
@base.check_constraint_exists?(name, *args, **options)
901+
end
902+
892903
private
893904
def raise_on_if_exist_options(options)
894905
unrecognized_option = options.keys.find do |key|

activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1227,16 +1227,24 @@ def check_constraint_options(table_name, expression, options) # :nodoc:
12271227
options
12281228
end
12291229

1230-
# Removes the given check constraint from the table.
1230+
# Removes the given check constraint from the table. Removing a check constraint
1231+
# that does not exist will raise an error.
12311232
#
12321233
# remove_check_constraint :products, name: "price_check"
12331234
#
1235+
# To silently ignore a non-existent check constraint rather than raise an error,
1236+
# use the `if_exists` option.
1237+
#
1238+
# remove_check_constraint :products, name: "price_check", if_exists: true
1239+
#
12341240
# The +expression+ parameter will be ignored if present. It can be helpful
12351241
# to provide this in a migration's +change+ method so it can be reverted.
12361242
# In that case, +expression+ will be used by #add_check_constraint.
12371243
def remove_check_constraint(table_name, expression = nil, **options)
12381244
return unless supports_check_constraints?
12391245

1246+
return if options[:if_exists] && !check_constraint_exists?(table_name, **options)
1247+
12401248
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
12411249

12421250
at = create_alter_table(table_name)
@@ -1437,6 +1445,10 @@ def use_foreign_keys?
14371445
end
14381446

14391447
private
1448+
def check_constraint_exists?(table_name, **options)
1449+
check_constraint_for(table_name, **options).present?
1450+
end
1451+
14401452
def validate_change_column_null_argument!(value)
14411453
unless value == true || value == false
14421454
raise ArgumentError, "change_column_null expects a boolean value (true for NULL, false for NOT NULL). Got: #{value.inspect}"

activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def add_check_constraint(table_name, expression, **options)
103103
end
104104

105105
def remove_check_constraint(table_name, expression = nil, **options)
106+
return if options[:if_exists] && !check_constraint_exists?(table_name, **options)
107+
106108
check_constraints = check_constraints(table_name)
107109
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
108110
check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }

activerecord/test/cases/migration/check_constraint_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ def test_remove_check_constraint
184184
assert_empty @connection.check_constraints("trades")
185185
end
186186

187+
def test_removing_check_constraint_with_if_exists_option
188+
@connection.add_check_constraint :trades, "quantity > 0", name: "quantity_check"
189+
190+
@connection.remove_check_constraint :trades, name: "quantity_check"
191+
192+
error = assert_raises ArgumentError do
193+
@connection.remove_check_constraint :trades, name: "quantity_check"
194+
end
195+
196+
assert_equal "Table 'trades' has no check constraint for {:name=>\"quantity_check\"}", error.message
197+
198+
assert_nothing_raised do
199+
@connection.remove_check_constraint :trades, name: "quantity_check", if_exists: true
200+
end
201+
end
202+
187203
def test_remove_non_existing_check_constraint
188204
assert_raises(ArgumentError) do
189205
@connection.remove_check_constraint :trades, name: "nonexistent"

0 commit comments

Comments
 (0)