如何测试(ActiveRecord)对象的相等性

chi*_*ior 51 ruby activerecord identity equality ruby-on-rails

Ruby 1.9.2on中Rails 3.0.3,我试图测试两个Friend(类继承自ActiveRecord::Base)对象之间的对象相等性.

对象相等,但测试失败:

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?)
Run Code Online (Sandbox Code Playgroud)

只是为了笑容,我还测试了对象身份,它失败了,正如我所料:

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.
Run Code Online (Sandbox Code Playgroud)

有人可以向我解释为什么对象相等的第一次测试失败,以及我如何成功断言这两个对象是否相等?

noo*_*odl 47

Rails故意将相等性检查委托给标识列.如果您想知道两个AR对象是否包含相同的内容,请比较两者上调用#attributes的结果.

  • 但在这种情况下,两个实例的标识列都是"nil",因为它们都没有保存.`eql?()`检查属性的值和类型.`nil.class == nil.class`是`true`和`nil == nil`是'true`,所以OP的第一个例子应该仍然是真的.你的答案并没有解释为什么它返回错误. (4认同)
  • 它不只是盲目地比较id,它只比较id,如果id是有意义的.正如安迪·林德曼(Andy Lindeman)的回答所述:"根据定义,新记录与任何其他记录不同". (3认同)

And*_*man 40

查看有关(别名)操作的API文档==eql?ActiveRecord::Base

如果comparison_object是完全相同的对象,则返回true,或者compare_object属于同一类型且self具有ID且等于comparison_object.id.

请注意,根据定义,新记录与任何其他记录不同,除非另一记录是接收者本身.此外,如果您使用select获取现有记录并将ID保留,您自己就可以使用,此谓词将返回false.

另请注意,销毁记录会在模型实例中保留其ID,因此已删除的模型仍具有可比性.


Tyl*_*ick 20

如果你想比较根据其属性两种型情况下,你可能会想排除从您比较某些无关的属性,如:id,created_at,和updated_at.(我会认为那些关于记录的元数据比记录数据本身的一部分更多.)

当您比较两个新的(未保存的)记录时,这可能无关紧要(因为id,created_at并且updated_atnil一直保存),但我有时发现有必要将保存的对象与保存的对象进行比较(在这种情况下==会给你假,因为没有!= 5).或者我想比较两个保存的对象以确定它们是否包含相同的数据(因此ActiveRecord ==运算符不起作用,因为如果它们具有不同id的,则返回false ,即使它们在其他方面相同).

我对这个问题的解决方案是在你想要使用属性进行比较的模型中添加这样的东西:

  def self.attributes_to_ignore_when_comparing
    [:id, :created_at, :updated_at]
  end

  def identical?(other)
    self. attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s)) ==
    other.attributes.except(*self.class.attributes_to_ignore_when_comparing.map(&:to_s))
  end
Run Code Online (Sandbox Code Playgroud)

然后在我的规范中,我可以编写如此可读和简洁的东西:

Address.last.should be_identical(Address.new({city: 'City', country: 'USA'}))
Run Code Online (Sandbox Code Playgroud)

我正在计划分支active_record_attributes_equality宝石并更改它以使用此行为,以便更容易重复使用.

但是我的一些问题包括:

  • 这样的宝石是否已经存在?
  • 该方法应该被称为什么?我不认为覆盖现有的==运算符是一个好主意,所以现在我正在调用它identical?.不过,也许是这样practically_identical?attributes_eql?会更准确,因为如果他们不是检查严格相同的(某些属性被允许有所不同.)...
  • attributes_to_ignore_when_comparing太冗长了.如果他们想要使用gem的默认值,则不需要将其显式添加到每个模型中.也许允许使用类宏覆盖默认值ignore_for_attributes_eql :last_signed_in_at, :updated_at

欢迎评论......

更新:active_record_attributes_equality我写了一个全新的gem,active_record_ignored_attributes,而不是分叉,可以在http://github.com/TylerRick/active_record_ignored_attributeshttp://rubygems.org/gems/active_record_ignored_attributes上找到.