In Ruby 1.9.2
on Rails 3.0.3
, I'm attempting to test for object equality between two Friend
(class inherits from ActiveRecord::Base
) objects.
The objects are equal, but the test fails:
Failure/Error: Friend.new(name: 'Bob').should eql(Friend.new(name: 'Bob'))
expected #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
(compared using eql?)
Just for grins, I also test for object identity, which fails as I'd expect:
Failure/Error: Friend.new(name: 'Bob').should equal(Friend.new(name: 'Bob'))
expected #<Friend:2190028040> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
got #<Friend:2190195380> => #<Friend id: nil, event_id: nil, name: 'Bob', created_at: nil, updated_at: nil>
Compared using equal?, which compares object identity,
but expected and actual are not the same object. Use
'actual.should == expected' if you don't care about
object identity in this example.
Can someone explain to me why the first test for object equality fails, and how I can successfully assert those two objects are equal?
Take a look at the API docs on the
==
(aliaseql?
) operation forActiveRecord::Base
If you want to compare two model instances based on their attributes, you will probably want to exclude certain irrelevant attributes from your comparison, such as:
id
,created_at
, andupdated_at
. (I would consider those to be more metadata about the record than part of the record's data itself.)This might not matter when you are comparing two new (unsaved) records (since
id
,created_at
, andupdated_at
will all benil
until saved), but I sometimes find it necessary to compare a saved object with an unsaved one (in which case == would give you false since nil != 5). Or I want to compare two saved objects to find out if they contain the same data (so the ActiveRecord==
operator doesn't work, because it returns false if they have differentid
's, even if they are otherwise identical).My solution to this problem is to add something like this in the models that you want to be comparable using attributes:
Then in my specs I can write such readable and succinct things as this:
I'm planning on forking the
active_record_attributes_equality
gem and changing it to use this behavior so that this can be more easily reused.Some questions I have, though, include:
==
operator is a good idea, so for now I'm calling itidentical?
. But maybe something likepractically_identical?
orattributes_eql?
would be more accurate, since it's not checking if they're strictly identical (some of the attributes are allowed to be different.)...attributes_to_ignore_when_comparing
is too verbose. Not that this will need to be explicitly added to each model if they want to use the gem's defaults. Maybe allow the default to be overridden with a class macro likeignore_for_attributes_eql :last_signed_in_at, :updated_at
Comments are welcome...
Update: Instead of forking the
active_record_attributes_equality
, I wrote a brand-new gem, active_record_ignored_attributes, available at http://github.com/TylerRick/active_record_ignored_attributes and http://rubygems.org/gems/active_record_ignored_attributesRails deliberately delegates equality checks to the identity column. If you want to know if two AR objects contain the same stuff, compare the result of calling #attributes on both.