如何testing(ActiveRecord)对象相等

Rails 3.0.3 Ruby 1.9.2 ,我试图testing两个Friend (从ActiveRecord::Baseinheritance的类)对象之间的对象相等性。

对象相同,但是testing失败:

 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?) 

只是为了咧嘴笑,我也testing对象的身份,这不符合我的预期:

 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. 

有人可以向我解释为什么对象平等的第一个testing失败,我怎样才能成功地断言这两个对象是平等的?

Rails故意将平等检查委托给标识列。 如果你想知道两个AR对象是否包含相同的东西,比较两个调用#attributes的结果。

查看ActiveRecord::Base== (alias eql? )操作的API文档

如果compare_object是相同的确切对象,则返回true,或者compare_object是相同的types,并且self有一个ID,它等于compare_object.id。

请注意 ,除非另一个logging是接收者本身,否则新logging根据定义与任何其他logging不同 。 此外,如果您使用select获取现有logging并将ID保留出来,那么您是独立的,这个谓词将返回false。

还要注意,破坏logging会保留模型实例中的ID,因此删除的模型仍然具有可比性。

如果要根据属性比较两个模型实例,则可能希望从比较中排除某些不相关的属性,如: idcreated_atupdated_at 。 (我认为这些是关于logging的更多元数据 ,而不是logging数据本身的一部分。)

当你比较两个新的(未保存的)logging(因为idcreated_atupdated_at都是nil直到保存),这可能无关紧要,但我有时会发现有必要比较一个保存的对象与 保存的对象(在这种情况下= =会给你错误,因为无!= 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 

然后在我的规范中,我可以写这样的可读和简洁的东西:

 Address.last.should be_identical(Address.new({city: 'City', country: 'USA'})) 

我正在计划分叉active_record_attributes_equality gem并将其更改为使用此行为,以便可以更容易地重用。

我有一些问题,包括:

  • 这样的gem已经存在了吗?
  • 该调用什么方法? 我不认为覆盖现有的==运算符是一个好主意,所以现在我称它是identical? 。 但也许像practically_identical?东西practically_identical?attributes_eql? 会更准确,因为它不检查它们是否完全相同( 某些属性允许不同)。
  • attributes_to_ignore_when_comparing过于冗长。 不是,如果他们想使用gem的默认值,这将需要明确地添加到每个模型。 也许允许使用像ignore_for_attributes_eql :last_signed_in_at, :updated_at这样的类macros来覆盖默认值ignore_for_attributes_eql :last_signed_in_at, :updated_at

欢迎评论…

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

  META = [:id, :created_at, :updated_at, :interacted_at, :confirmed_at] def eql_attributes?(original,new) original = original.attributes.with_indifferent_access.except(*META) new = new.attributes.symbolize_keys.with_indifferent_access.except(*META) original == new end eql_attributes? attrs, attrs2