ActiveRecord equality - explained

ActiveRecord equality - unapparent

Comparing objects in Ruby there is distinct difference between object equality (== and eql?) and object identity (equal?). Both are defined at class Object. And for all inheriting sub classes: object equality is meant to be overwritten, though object identity is not.
That also applies to ActiveRecord.
A simple model example:

class Language < ActiveRecord::Base
end

At first a new object and its duplicate look very equal:

ruby = Language.new name: 'Ruby'
# => #<Language id: nil, name: "Ruby">
ruby.dup
# => #<Language id: nil, name: "Ruby">

As expected both objects are not identical:

ruby.equal? ruby.dup
# => false

The object equality should be verified in a RSpec test.

ActiveRecord equality - tested

It is expected that a language and its duplicate not only look equal, but also are equal:

require 'spec_helper'

describe Language, type: :model do
  subject { Language.new name: 'Ruby' }

  context "when duplicated" do
    it { is_expected.to eq(subject.dup) }
  end
end

However the test does not verify the assertion:

1) Language when duplicated should eq #<Language id: nil, name: "Ruby">
   Failure/Error: it { is_expected.to eq(subject.dup) }
     
     expected: #<Language id: nil, name: "Ruby">
          got: #<Language id: nil, name: "Ruby">
     
     (compared using ==)

       Diff:
       @@ -1,4 +1,4 @@
       -#<Language:0x00000003e59248
       +#<Language:0x000000056bca10

The reason for that behaviour can be found in the ActiveRecord implementation itself.

ActiveRecord equality - implemented

The method == was overwritten in ActiveRecord:

# active_record/core.rb
module ActiveRecord
  module Core

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

  end
end

ActiveRecord objects only are equal, if they have a defined and equal ID.

ActiveRecord equality - overwritten

The default behaviour can be changed for sure. An appropriate object equality implementation:

class Language < ActiveRecord::Base
  def == comparison_object
    self.attributes == comparison_object.attributes
  end
end

Languages should be equal, if their attributes (name) are equal accordingly.
The RSpec test verifies the behaviour:

Finished in 0.00517 seconds (files took 0.75063 seconds to load)
1 example, 0 failures