Rubyのオブジェクト比較

Rubyのオブジェクト比較

最近Rubyのオブジェクト比較について凡ミスを犯したので戒めのためにここに記します。

RspecのテストでオブジェクトがNewされたことをテストしたかったので以下のサンプルの
ようなテストを書いてみました。

class Hello
  @message
  def initialize(message: nil)
    @message = message
  end

  def self.greeting
    @message
  end
end

describe Hello do
  let(:hello) { Hello.new(message: 'Hello World!') }

  example 'initialize' do
    expect(hello).to eq Hello.new(message: 'Hello World!')
  end
end

Newしているインスタンス同士を比較しているので一見通るようにみえるのですが実際は通りません。
実行してみると、Rspecが以下のようにエラーの原因を適切に返してくれました。

Failures:

1) Hello initialize
Failure/Error: expect(hello).to eq Hello.new(message: 'Hello World!')

expected: #
got: #

(compared using ==)

Diff:
@@ -1,2 +1,2 @@
-#
+#
# ./sample_spec.rb:16:in `block (2 levels) in '

Finished in 0.0289 seconds (files took 0.17197 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./sample_spec.rb:15 # Hello initialize

オブジェクトidが違う=(違うオブジェクト)なので通らないということです。
同値性と同一性の違いですね。
同じ引数を渡してNewした上記のクラスは、同値ですが同一ではありません。
一致させるのであれば、==メソッドを再定義するという方法があります。

class Hello
  attr_accessor :message

  def initialize(message: nil)
    @message = message
  end

  def ==(obj)
    @message == obj.message
  end

  def self.greeting
    @message
  end
end

Specが通りました。

Finished in 0.00125 seconds (files took 0.1751 seconds to load)
1 example, 0 failures

ちなみにRubyには以下のようにいくつかの同値、同一判定のメソッドがあります。

演算子 内容
== 同値かどうかの比較
=== caseで使われる比較で通常は==の比較と同じだが、オブジェクトによって変わる
equal? 同一かどうかの比較
eql? ハッシュの内部で同じキーかどうかを調べるために使われるメソッド

eql?はハッシュ以外の場合にオブジェクトによって挙動がかわります。
例えばObjectクラスのeql?では、equal?と同じになっていますが、StringやArrayなどでは
メソッドが上書きされていて、値が同じかどうかの判定になります。

irb(main):004:0> Object.new.eql? Object.new
=> false
irb(main):005:0> "abc".eql? "abc"
=> true
irb(main):006:0> [1,2,3].eql? [1,2,3]
=> true

TAG

  • このエントリーをはてなブックマークに追加
金子 将範
エンジニア 金子 将範 rubyist

新しいことや難しい課題に挑戦することにやりがいを感じ、安定やぬるい事は退屈だと感じます。 考えるより先に手が動く、肉体派エンジニアで座右の銘は諸行無常。 大事なのは感性、プログラミングにおいても感覚で理解し、感覚で書きます。