`add_column`でPKに`null: true`を指定すると`ArgumentError`を発生させるように変更
add_columnでプライマリキーにnull: trueを指定した場合、暗黙に無視されていた挙動を改め、ArgumentErrorを発生させるようになりました。ユーザーが意図しない無効な呼び出しを明示的なエラーで知らせる設計への改善です。
背景
プライマリキーには無条件でNOT NULL制約が付与されるため、null: trueを指定しても実際には反映されないという問題がありました。schema_definitions.rbのnew_column_definitionメソッドでは、options[:primary_key]が真の場合にoptions[:null] = falseを強制適用していたため、null: trueの指定は黙って上書きされていました。
この変更のきっかけはドキュメント改善でした。PR作者がadd_columnの:nullオプションの説明を明確にしようとしたところ、PKに対して「このオプションは無視される」という挙動を言語化する過程で、それが正しくない設計であると気づいたと説明されています。無効な指定を黙って無視するより、エラーを発生させてユーザーに知らせるべきという判断に至っています。
技術的な変更
schema_definitions.rbのnew_column_definitionメソッドにおいて、PKにnull: trueが指定された場合にArgumentErrorを発生させる分岐が追加されました。
変更前:
options[:primary_key] ||= type == :primary_key
options[:null] = false if options[:primary_key]
create_column_definition(name, type, options)
変更後:
options[:primary_key] ||= type == :primary_key
if options[:primary_key]
if options[:null]
raise ArgumentError, "primary keys cannot be NULL"
end
options[:null] = false
end
create_column_definition(name, type, options)
これにより、以下の2つのパターンがどちらもArgumentErrorを発生させます:
-
add_column :table, :other_id, :primary_key, null: true(型でprimary_keyを指定) -
add_column :table, :another_id, :integer, primary_key: true, null: true(オプションでprimary_key: trueを指定)
nullがnilまたはfalseの場合は従来通り動作します。null: trueを明示的に指定した場合のみエラーとなるため、既存のadd_column呼び出しでnullを指定していないコードへの影響はありません。
また、SQLite3アダプターのcopy_tableメソッドで、PKカラムに対してcolumn.nullをそのまま渡していたバグも修正されました。SQLiteではCREATE TABLE foo (id INTEGER PRIMARY KEY)のように定義されたPKでもcolumn.nullがtrueを返すという挙動があり、copy_tableがこの値をそのまま渡すと新しい制約のもとでエラーになっていました。
変更前:
{
limit: column.limit,
precision: column.precision,
scale: column.scale,
null: column.null,
collation: column.collation,
primary_key: column_name == from_primary_key
}
変更後:
{
limit: column.limit,
precision: column.precision,
scale: column.scale,
collation: column.collation,
primary_key: column_name == from_primary_key
}
# PKカラムでは null の引き渡しをスキップする
column_options[:null] = column.null unless column_name == from_primary_key
PKカラムに対してはnullオプションを渡さないことで、NOT NULL制約の自動付与に委ねる設計になっています。
設計判断
「暗黙の無視」から「明示的なエラー」へという方針が採られました。
null: trueの指定をnull: falseに静かに上書きする従来の挙動は、ユーザーが意図した設定が反映されないままになるという点で問題があります。null: falseの指定であれば意図と結果が一致しますが、null: trueは明確に意図と反する結果を生みます。このギャップをエラーで表面化させることで、呼び出し側のコードに潜む誤りを早期に発見できるようになります。
options[:null]のtruthy評価(if options[:null])を条件にしている点も注目です。nullがnilの場合はフォールスルーしてoptions[:null] = falseが設定されるため、nullを指定しないコードは影響を受けません。エラーになるのはnull: trueを積極的に書いた場合のみです。
まとめ
この変更は、「無効な引数は無視するより早期に失敗させる」というFail-Fastの原則をActive Recordのスキーマ定義層に適用したものです。ドキュメントを書く行為が潜在的な設計上の問題を発見するきっかけになったという点で、ドキュメント作成が品質向上に直接貢献した好例といえます。