views:

110

answers:

2

I have two classes with a has_many and belongs_to association:

class Employee < ActiveRecord::Base
  has_many :contracts
end
class Contract < ActiveRecord::Base
  belongs_to :employee
end

I expect that the employee returned by the #employee method of the Contract class would be equal to itself, which means that the following unit test would pass.

class EmployeeTest < ActiveSupport::TestCase
  test "an object retrieved via a belongs_to association should be equal to itself" do
    e = Employee.new
    e.contracts << Contract.new
    assert e.save
    a = e.contracts[0].employee
    assert a.equal? a
  end
end

However, it fails. I do not understand. Is this a bug in ActiveRecord?

Thanks for helping out.

+3  A: 

This has to do with object equality. consider this IRB session

irb(main):010:0> Foo = Class.new
=> Foo
irb(main):011:0> f = Foo.new
=> #<Foo:0x16c128>
irb(main):012:0> b = Foo.new
=> #<Foo:0x1866a8>
irb(main):013:0> f == b
=> false

By default, == will test that the two objects have the same type, and same object_id. In activerecord, it is hitting up the database for the first employee, and hitting it up again for the employee through the referencial method, but those are two different objects. Since the object_ids are different, it doesn't matter if they have all the same values, == will return false. To change this behavior, consider this second IRB session

irb(main):050:0> class Bar
irb(main):051:1> attr_accessor :id
irb(main):052:1> def ==(compare)
irb(main):053:2> compare.respond_to?(:id) && @id == compare.id
irb(main):054:2> end
irb(main):055:1> end
=> nil
irb(main):056:0> a = Bar.new
=> #<Bar:0x45c8b50>
irb(main):057:0> b = Bar.new
=> #<Bar:0x45c2430>
irb(main):058:0> a.id = 1
=> 1
irb(main):059:0> b.id = 1
=> 1
irb(main):060:0> a == b
=> true
irb(main):061:0> a.id = 2
=> 2
irb(main):062:0> a == b
=> false

Here I defined the == operator to compare the .id methods on the two objects (or to just return false if the object we are comparing doesn't have an id method). If you want to compare Employees by value like this, you will have to define your own == method to implement it.

Matt Briggs
Sorry, but your answer misses the point I wanted to make. I do not want to implement an equality test to check that two objects are the same.I just want to understand why an object retrieved via a belongs_to association is not equal to itself. How is that possible? I would expect that for any object x, x.equal?(x) is true, but apparently for objects retrieved via a belongs_to association, x.equal?(x) is false.
Koen De Hondt
A: 

That is probably because the rails internal cached differently in two association calls. try to do

  a.reload.equal? a.reload

this will get rid of the caching and should return true

ez