マルチパラメータ属性代入で `nil` を `""` と同等に扱うよう修正

rails/rails

Active Recordのマルチパラメータ属性代入において、nil 値が渡された際に発生していた NoMethodError を修正しました。value.empty?value.blank? に置き換えるだけの1行の変更で、nil"" が意味的に等価に扱われるようになります。

背景

マルチパラメータ属性代入は、Date 型や DateTime 型のカラムに対して "last_read(1i)" のような分割されたキーでハッシュを渡すと、Active Recordが1つの値に合成する仕組みです。フォームから年・月・日を別々のフィールドで受け取るケースで利用されます。

これまで、各パラメータに "" を渡した場合は「値が不在」として nil に変換されていました。しかし、同じく「値が不在」を表す nil を渡すと、value.empty?NilClass に対して呼ばれ NoMethodError が発生していました。フォームの実装やデータの変換処理によっては "" ではなく nil が渡されることがあり、予期しない例外の原因となっていました。

topic = Topic.where.not(last_read: nil).first

# "" の場合: 正常動作
topic.attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
topic.last_read # => nil

# nil の場合: NoMethodError が発生
topic.attributes = { "last_read(1i)" => nil, "last_read(2i)" => nil, "last_read(3i)" => nil }
# => NoMethodError: undefined method 'empty?' for nil

""nil はどちらも「値が存在しない」ことを示す点で意味的に同等であり、同じ結果(nil)を返すべき入力です。

技術的な変更

変更箇所は activerecord/lib/active_record/attribute_assignment.rb の1行のみです。extract_callstack_for_multiparameter_attributes メソッド内で value.empty?value.blank? に置き換えました。

変更前:

parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)

変更後:

parameter_value = value.blank? ? nil : type_cast_attribute_value(multiparameter_name, value)

String#empty?String にしか定義されておらず、nil に対して呼び出すと NoMethodError になります。一方、Object#blank? はActive Supportが NilClass を含むあらゆるオブジェクトに定義しており、nil.blank?true を返します。String#blank? の内部では String#empty? が呼ばれるため、"" に対する既存の挙動は変わりません。

あわせて activerecord/test/cases/multiparameter_attributes_test.rbtest_multiparameter_attributes_on_date_with_all_nil テストが追加され、既存の test_multiparameter_attributes_on_date_with_all_empty と対になる形でリグレッションが防止されています。

設計判断

blank? への置き換えという最小限のアプローチ が採用されました。nil を事前に "" に変換するガード節を追加するなど他の修正方法も考えられますが、blank?nil"" の両方で true となるため、1文字の変更で両方のケースを一元的に処理できます。

Active Supportの blank? を利用することで、条件分岐を増やさずにRailsの既存のヘルパーメソッドに処理を委譲しています。Active Recordは既にActive Supportに依存しているため、外部依存が増えることもありません。blank?nil / "" / 空白文字列のいずれにも true を返す点は、フォーム入力の正規化という文脈でも自然な選択です。

まとめ

value.empty?value.blank? に変えるだけの1行修正ですが、nil"" という意味的に等価な2つの入力を一貫して扱えるようにすることで、入力の正規化に関する暗黙の前提を取り除きました。Active Supportの blank? を活用することで、コードの変更量を最小限に抑えつつ、マルチパラメータ属性代入の堅牢性が向上しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
e3565c01

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ OK

Title, Context, Technical Detailの存在と明確さ

カスタムMarkdown構文 ✓ OK

シンタックスハイライト・GitHubリンク記法の正確性

対象読者への適合性 ✓ OK

エンジニア向けの適切な技術レベルと表現

パラグラフ・ライティング ✓ OK

トピックセンテンス・1段落1トピック・段落長

Diff内容との照合 ✓ OK

コードブロックとDiff内容の一致

技術用語の正確性 ✓ OK

技術用語の正確な使用

説明の技術的正確性 ✓ OK

技術的主張の正確性と論理性

事実の突合 ✓ OK

PR情報による主張の裏付け(ハルシネーション検出)

数値・固有名詞の確認 ✓ OK

PR番号・コミットID・バージョン等の正確性

タイトル・説明との一致 ✓ OK

記事タイトル・説明とPR内容の一致

外部知識の正確性 ✓ OK

PRに記載のない外部知識(LTS、サポート状況など)の不使用

時間表現の正確性 ✓ OK

時間表現がPR情報と一致しているか