こんにちは
Rubyでアプリを作っていたんですが
終盤になってデバッグに悩まされたことをまとめておきました。
もしも同じエラーで悩んでいる方がいて、参考になれば幸いです。
事の発端
「あれ、さっきまでエラーじゃなかったのに」
知り合いとバグを探していたところ、エラーが解決したのにエラーが同じところで再発したのが事の発端でした。
目的は 配列の中から指定した条件で一つのオブジェクトを削除する でした。
配列.select!で行うとclassが異なる
目的はシンプルなものなので、オブジェクトが複数格納された配列の中から条件に見合ったものを抜き出して、
プログラムが実行されると一つだけ削除する。
なので、配列の入ったものに対して破壊的メソッドを定義しようとこのときはselect!
を使用しました.
@ary = [1,2,3,4,5]
@ary.select!{|n| n == 2}.shift
シンプルに書き直すとこんな感じでしたが、エラーで撃沈します。
そもそもshiftはArryaクラスの破壊的メソッドです。
特徴はshift(引数)の引数に数字を入れておくと、配列の中から引数に与えられた数のオブジェクトを削除するもの。
Ruby3.1のリファレンスマニュアルをちゃんと読めば、select!だと条件に合った場合はnil、そうでないならselfを返すようなっています。
なので、このままだとshift
はArrayクラスのメソッドなのでそもそも使えないんですよね。
だって相手がselect!だとself
かnil
ですので( ´∀`)
配列から「抜き出して削除」?それとも?
そもそも破壊的に削除するにはどうすればいいか、という問題がありました。
この時の自分は、最初にfilter
かselect
をして条件に見合ったものを最初に抜き出す必要があると思っていました。
そこで試したのが配列.select{条件}.shift。
ただこのプログラムだとエラーにはなりませんでした。
しかし、エラーは出ないけれど目的とは異なり非破壊的メソッドになってしまい、
当初の目的とは違うものとなります。
class Cigar
attr_reader :name
def initialize(name)
@name = name
end
end
list = []
for num in 1..5 do
peace = Cigar.new("peace")
lucky = Cigar.new("lucky_strike")
list << peace
list << lucky
end
list.select{|n| n.name == "peace"}.shift(1)
##==>> エラーにはならないけど、削除したオブジェクトが元に戻っている(つまり非破壊的)
ターミナルには「削除したよ!」と該当のオブジェクトを削除したのが表示されます。
でも、破壊的ではないので今回で言えばlistを呼び出すと削除したはずのオブジェクトが元に戻ってしまっているのが目に見えます。
たどり着いたのは【配列.deledte_at】
この時、デバッグにpryを使っていたのでbinding.pry
を使用してインスタンス変数の中身などを確認しつつ色々試していました。
考え方を改めて、
配列に対して最初にselectなどで条件に見合ったオブジェクトを探すのではなく、
後から条件を指定して削除するべきなのでは!?
そこで配列.delete_atの登場です。
list.delete_at(list.find_index{|n| n.name == "lucky_strike"})
これで配列の中から
条件に見合ったオブジェクトを一つ削除することに成功しました!
まとめ
配列を操作できることはエンジニアにとって基礎中の基礎だと思います。
ですが、私自身初学者なのでやはり理解度が足りずエラーに悩まされることは多くあります。
しかし、調べてtry&error を繰り返すことで成長できることを実感できた問題でした。
今回はdelete_at
を使用しました。
find_index
は条件と一致した要素が含まれるオブジェクトの位置を教えてくれるメソッドです。
一致した位置は配列の中にあるオブジェクトのインデックス番号なのでそれをintでdelete_atに渡し、オブジェクトを一つだけ削除できました。
おそらくshiftなどでも同じように処理することが可能だと思います。
何かしらのお役に立てれば幸いです!