remove_check_constraintのif_existsが式形式でも正しく動作するよう修正
remove_check_constraint に if_exists: true を指定した際、式(expression)で指定したチェック制約が存在しないと ArgumentError が発生し、期待通りの no‑op にならなかった問題を解消します。
背景
remove_check_constraint は制約名または 式 のどちらでも削除でき、if_exists オプションは対象が見つからない場合に例外を抑制するために提供されています。実装上は check_constraint_exists? に制約情報を渡して存在チェックを行う設計です。
しかし、式形式で呼び出したとき if_exists の判定が expression をオプションに含めず に check_constraint_exists? を呼び出していたため、name も expression も渡らず ArgumentError: At least one of :name or :expression must be supplied がスローされました。結果として if_exists: true が期待通りに機能せず、マイグレーションの可逆性が損なわれていました。
技術的な変更
抽象アダプタ (abstract/schema_statements.rb) では、存在チェックの呼び出しに expression: expression を追加しました。
@@ -1445,7 +1445,7 @@ def check_constraint_options(table_name, expression, options) # :nodoc:
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
return unless supports_check_constraints?
- return if if_exists && !check_constraint_exists?(table_name, **options)
+ return if if_exists && !check_constraint_exists?(table_name, expression: expression, **options)
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
この変更により、if_exists 判定が式情報を正しく参照でき、制約が存在しない場合は単にリターンし、例外は発生しません。
SQLite3 アダプタ (sqlite3/schema_statements.rb) でも同様のパッチを適用し、オーバーライド実装でも一貫した動作を保証しています。
@@ -101,7 +101,7 @@ def add_check_constraint(table_name, expression, **options)
end
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
- return if if_exists && !check_constraint_exists?(table_name, **options)
+ return if if_exists && !check_constraint_exists?(table_name, expression: expression, **options)
check_constraints = check_constraints(table_name)
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
テストケース が追加され、式形式で if_exists: true を指定したとき例外が発生しないことを明示的に確認しています。
@@
@connection.remove_check_constraint :trades, name: "quantity_check", if_exists: true
end
+ assert_nothing_raised do
+ @connection.remove_check_constraint :trades, "quantity > 0", if_exists: true
+ end
+
error = assert_raises ArgumentError do
@connection.remove_check_constraint :trades, name: "quantity_check"
end
これにより、式指定でも if_exists が正しく機能することがテストで保証されました。
設計判断
本修正は API のシグネチャを変更せず、内部ロジックだけを調整することで互換性を維持しています。既存の remove_check_constraint(table, name: ..., if_exists: true) の呼び出しは影響を受けず、式指定を利用しているコードだけが期待通りに動作するようになります。
また、ドキュメントで推奨されている式形式 と実装を合わせることで、マイグレーションの可逆性が保証されます。if_exists が正しく機能すれば、制約が削除済みでも安全にマイグレーションを再実行できるため、開発フローが一層堅牢になります。
まとめ
この PR は remove_check_constraint の if_exists オプションが式形式でも正しく動作しないバグを修正し、抽象アダプタと SQLite3 アダプタでロジックを統一しました。テスト追加により回帰が防止され、マイグレーションの可逆性と開発者体験が向上します。