self を返す破壊的メソッドの中には nil をたまに返すものがあるので、新しいオブジェクトを返すメソッドの単純な置き換えとして使う際は注意がいる。
# 置き換えても一見うまく動いているが…
str = "ABC\n"
str.downcase.chomp #=> "abc"
str.downcase!.chomp! #=> "abc"
# たまにおかしくなる場合がある
str = 'abc'
str.downcase.chomp #=> "abc"
str.downcase!.chomp! #=> NoMethodError: undefined method `chomp!' for nil:NilClass
では nil を返しうるメソッドと self を必ず返すメソッドの差は何か、ということが気になったので、よく使う String, Array, Hash クラスについてメソッドを調べてみた。結果として単純明快な規則までは分からなかったものの、種類毎にどちらのパターンか決まっている印象を受けた。
(ActiveSupportについては同じ規則でなく、実装の都合で決まっているように見える)
Ruby
Ruby 2.5のリファレンスマニュアルから抜粋し、覚えやすいよう大まかな役割毎に分類した。(正確な動作説明はマニュアルを参照)
String
※ まともに書くと長いので、複数のメソッド名を正規表現でまとめている
-
selfまたはnilを返す破壊的メソッド- 特定文字の除去 :
delete(|_prefix|_suffix)!,chomp!,chop!,[lr]?strip!,squeeze! - 大文字小文字の変換 :
upcase!,downcase!,swapcase!,capitalize! - 置換 :
sub!,gsub!,tr!,tr_s!
- 特定文字の除去 :
- 必ず
selfを返す破壊的メソッド- 初期化 :
clear,replace - 文字列の追加 :
<<,concat,insert - 並べ替え :
reverse! - 次の文字列 :
succ!,next! - エンコーディング :
encode!,force_encoding,scrub!,unicode_normalize!
- 初期化 :
Array
-
selfまたはnilを返す破壊的メソッド- 抽出 :
select!,reject!,compact!,uniq! - 変形 :
flatten!
- 抽出 :
- 必ず
selfを返す破壊的メソッド- 初期化 :
clear,fill,replace - 要素の追加 :
<<,concat,insert,push,append,unshift,prepend - 並べ替え :
sort!,sort_by!,shuffle!,reverse!,rotate! - 抽出 :
keep_if,delete_if - 要素の変換 :
collect!,map!
- 初期化 :
Hash
-
selfまたはnilを返す破壊的メソッド- 抽出 :
select!,reject!,compact!
- 抽出 :
- 必ず
selfを返す破壊的メソッド- 初期化 :
clear,replace - 追加・更新 :
update,merge! - 抽出 :
keep_if,delete_if - 要素の変換 :
transform_keys!,transform_values! - 再計算 :
rehash - 設定変更 :
compare_by_identity
- 初期化 :
ActiveSupport
ActiveSupport 5.2.0のソースコードから抜粋した。新しいRubyに取り込まれたものについても記載する。
String
-
selfまたはnilを返す破壊的メソッド- 置換 :
indent!
- 置換 :
- 必ず
selfを返す破壊的メソッド- 特定文字の除去 :
remove! - 置換 :
squish!
- 特定文字の除去 :
Array
-
selfまたはnilを返す破壊的メソッド- (無し)
- 必ず
selfを返す破壊的メソッド- 要素の追加 :
append,prepend
- 要素の追加 :
Hash
※ まともに書くと長いので、複数のメソッド名を正規表現でまとめている
-
selfまたはnilを返す破壊的メソッド- 抽出 :
compact!
- 抽出 :
- 必ず
selfを返す破壊的メソッド- 追加・更新 :
deep_merge!,reverse_(merge!|update),with_defaults! - 抽出 :
except! - 要素の変換 :
transform_values!,(deep_)?(transform|stringify|symbolize)_keys!,to_options!
- 追加・更新 :
補足
上に挙げた「self または nil を返す破壊的メソッド」は全て、実際に nil を返すのはオブジェクトに手を加えなかったとき。例えば置換の場合、全く同じ文字列に置換すれば nil とはならない。
str = 'abc'
str.tr!('A', 'A') #=> nil
str.tr!('a', 'a') #=> "abc"
必ず self を返してほしいなら、Object#tap を使えば安全。
str = 'abc'
str.tap { |obj| obj.tr!('A', 'A') } #=> "abc"
str.tap(&:downcase!).tap(&:chomp!) #=> "abc"