【Rails】ActiveRecordでオブジェクトどうしを==で比較したとき、何が比較されるか

ActiveRecordで取得したオブジェクトが等しいかどうか、==で比較したい場合があります。

オブジェクトどうしを==で比較するとき、何を比較しているかがあいまいなため、調べました。

何を比較しているか

公式ドキュメントであるRuby on Rails APIからActiveRecord::Core#==を参照すると、

Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.

とあります。大雑把に訳すと

比較対象のオブジェクトが厳密に同じオブジェクトであるか、または同じクラスのインスタンスでIDというインスタンス変数を持ちが値を持ち1、さらにそれが同一であるときにtrueを返す2

となります。

つまり、ActiveRecordのインスタンスを==で比較するときは

  • オブジェクトIDが同一の、全く同じオブジェクトどうし
  • 同じテーブルの同じIDを持つレコードから作成されたオブジェクトどうし

のいずれかの場合にtrueが返るということになります。

注意する点

後者のルールによって、あるインスタンス変数では別の値を持っているのに、==で比較するとtrueと返ってくる状況がありえます。

片方のオブジェクトだけ編集された場合

例えば、同じテーブルの同じレコードから作成されたオブジェクトAとオブジェクトBがあるとします。

作成直後に両者を比較すると、当然trueが返ります。

obj_a = SampleModel.find(1)
obj_b = SampleModel.find(1)

obj_a == obj_b
=> true

しかし、このあとオブジェクトAの一部のデータだけが変更された場合に比較してもtrueが返ります。

obj_a.name == "another name"

obj_a == obj_b
=> true

です。この場合、変更後の値がDBに保存されているかどうかは関係ありません。

テーブルの主キーがIDでない場合

テーブルの主キーに何らかの事情でID以外が指定されている場合、IDが同じ全く別々のデータを比較してもtrueが返ってきます。

そんな特殊なケースでもIDという名前のカラムには最低限unique制約をつけておきましょう3

比較の例外1

一方で、IDが一致しているのにfalseが返る場合があります。

それが両辺のIDの値がともにnilである場合です。例えば、

SampleModel.new == SampleModel.new # => false

このような場合です。

==のソースコードを見ると、

rails/core.rb
def ==(comparison_object)
  super ||
    comparison_object.instance_of?(self.class) &&
    !id.nil? &&
    comparison_object.id == id
end

となっており、確かに左辺がnilでない条件が入っています。

左辺がnilの場合は問答無用でfalseが返ってしまうので、比較そのものが行われないんですね。

newしてsaveする前のID値はnilなので、ActiveRecordどうしの==での比較はID値の比較とだけ覚えていると思わぬ穴にハマります。

注意しましょう。

まとめ

  • ActiveRecordのオブジェクトどうしを==で比較するとき、生成元クラスおよびIDが一緒ならtrueが返る
  • 逆に、それ以外は見ていないので比較の際は注意
  • IDという名前のカラムには最低限unique制約をつけよう
  • IDがnilの場合は問答無用でfalseが返るので注意1

誤りや言葉足らずな点がありましたらコメントにてご指摘ください。


  1. (2018/05/19追記) コメントでの指摘を反映 

  2. the same type は「同じ型」と訳したくなりますが、Rubyに型なんてモノは存在しないので意訳しました 

  3. uniqueじゃないIDentifierって何なんだって話ですけど 

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.