gsub! は置換が発生しないと nil を返す(gsub との違い)
破壊的メソッドのgsub!やsub!は、置換対象が見つかって実際に変更が起きた場合は変更後の文字列を、何も置換されなかった場合はnilを返します。戻り値をそのまま次のメソッドにつなげると、置換が起きなかった入力でNoMethodErrorになります。
エラーメッセージの読み方
main.rb:3:in `<main>': undefined method `upcase' for nil (NoMethodError)
main.rb- ファイル名
3- 行番号 — 実際にクラッシュした行
<main>- 発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
NoMethodError- 例外クラス — 何が起きたか。ここを検索するのが最短です
undefined method `upcase' for nil- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 text = "hello" 2 result = text.gsub!("x", "y") ^ 3 puts result.upcase
gsub!は置換が1件も発生しないとnilを返します。置換が起きるとは限らない入力にはgsub!の戻り値をそのまま使うのは危険です。
直し方: gsub! を gsub にします。
パターン2
1 word = "ruby" 2 result = word.gsub!("z", "q") ^ 3 puts result.length
置換対象が見つからなかった場合、gsub!は変更前の文字列すら返さずnilになります。
直し方: gsub! を gsub にします。
パターン3
1 line = "sample" 2 result = line.gsub!("!", "?") ^ 3 puts result.reverse
常に文字列を返してほしいだけなら、破壊的でないgsubを使うのが安全です。
直し方: gsub! を gsub にします。
パターン4
1 note = "memo" 2 result = note.gsub!("x", "y") ^ 3 puts result.capitalize
gsub!の戻り値は「変更があったかどうか」を表す設計で、変更が無ければnilになります。
直し方: gsub! を gsub にします。
パターン5
1 title = "draft" 2 result = title.gsub!("q", "w") ^ 3 puts result.upcase
メソッドチェーンでgsub!をつなげる場合、置換が起きなかった経路でnilが混ざる危険を考える必要があります。
直し方: gsub! を gsub にします。
よくある誤解
「gsub!もgsubと同じく、変更後の文字列を常に返すはず」という思い込みは誤りです。gsub!は「変更があったかどうか」を戻り値で表現する設計になっており、変更が無ければnilが返ります。
実務での勘所
「変更が無ければnilを返す」という設計は、gsub!やsub!だけの特殊仕様ではなく、Rubyの!が付くメソッド全般に広く見られる緩やかな慣習です。ただしこれは言語仕様として保証された絶対のルールではなく、あくまで多くの標準ライブラリのメソッドが従っている設計上の慣習である点には注意が必要です。実務では、!付きメソッドの戻り値をメソッドチェーンでそのまま使う前に、そのメソッドのドキュメントで「変更が無い場合の戻り値」を確認する習慣が安全です。