Ruby-on-rails 如何测试 (ActiveRecord) 对象相等性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4738439/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 00:08:56  来源:igfitidea点击:

How to test for (ActiveRecord) object equality

ruby-on-railsrubyactiverecordidentityequality

提问by ryonlife

In Ruby 1.9.2on Rails 3.0.3, I'm attempting to test for object equality between two Friend(class inherits from ActiveRecord::Base) objects.

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

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?

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

采纳答案by noodl

Rails 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.

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

回答by Andy Lindeman

Take a look at the API docson the ==(alias eql?) operation for ActiveRecord::Base

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

Returns true if comparison_object is the same exact object, or comparison_object is of the same type and self has an ID and it is equal to comparison_object.id.

Note that new records are different from any other record by definition, unless the other record is the receiver itself. Besides, if you fetch existing records with select and leave the ID out, you're on your own, this predicate will return false.

Note also that destroying a record preserves its ID in the model instance, so deleted models are still comparable.

如果comparison_object 是完全相同的对象,或者comparation_object 是相同类型并且self 有一个ID 并且它等于comparison_object.id,则返回true。

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

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

回答by Tyler Rick

If you want to compare two model instances based on their attributes, you will probably want to excludecertain irrelevant attributes from your comparison, such as: id, created_at, and updated_at. (I would consider those to be more metadataabout the record than part of the record's data itself.)

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

This might not matter when you are comparing two new (unsaved) records (since id, created_at, and updated_atwill all be niluntil saved), but I sometimes find it necessary to compare a savedobject with an unsavedone (in which case == would give you false since nil != 5). Or I want to compare two savedobjects to find out if they contain the same data(so the ActiveRecord ==operator doesn't work, because it returns false if they have different id's, even if they are otherwise identical).

当您比较两个新的(未保存的)记录时,这可能无关紧要(因为id,created_atupdated_at将在nil保存之前全部保留),但我有时发现有必要将保存的对象与保存的对象进行比较(在这种情况下 == 会给你假,因为 nil != 5)。或者我想比较两个保存的对象以找出它们是否包含相同的数据(因此 ActiveRecord==运算符不起作用,因为如果它们具有不同的id's,即使它们在其他方面相同,它也会返回 false )。

My solution to this problem is to add something like this in the models that you want to be comparable using attributes:

我对这个问题的解决方案是在要使用属性进行比较的模型中添加类似的内容:

  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

Then in my specs I can write such readable and succinct things as this:

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

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

I'm planning on forking the active_record_attributes_equalitygem and changing it to use this behavior so that this can be more easily reused.

我计划分叉active_record_attributes_equalitygem 并将其更改为使用此行为,以便可以更轻松地重用它。

Some questions I have, though, include:

不过,我的一些问题包括:

  • Does such a gem already exist??
  • What should the method be called? I don't think overriding the existing ==operator is a good idea, so for now I'm calling it identical?. But maybe something like practically_identical?or attributes_eql?would be more accurate, since it's not checking if they're strictlyidentical (someof the attributes are allowed to be different.)...
  • attributes_to_ignore_when_comparingis 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 like ignore_for_attributes_eql :last_signed_in_at, :updated_at
  • 这样的宝石已经存在了吗??
  • 应该调用什么方法?我认为覆盖现有的==运算符不是一个好主意,所以现在我称之为identical?. 但也许类似practically_identical?attributes_eql?更准确的东西,因为它不检查它们是否严格相同(允许某些属性不同。)...
  • attributes_to_ignore_when_comparing太冗长了。并不是说如果他们想使用 gem 的默认值,这将需要显式添加到每个模型中。也许允许使用类宏覆盖默认值,例如ignore_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_attributesand http://rubygems.org/gems/active_record_ignored_attributes

更新active_record_attributes_equality我没有分叉,而是写了一个全新的 gem,active_record_ignored_attributes,可在http://github.com/TylerRick/active_record_ignored_attributeshttp://rubygems.org/gems/active_record_ignored_attributes 获得

回答by scott

 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

回答by Victor

I created a matcher on RSpec just for this type of comparison, very simple, but effective.

我在 RSpec 上创建了一个匹配器只是为了这种类型的比较,非常简单,但有效。

Inside this file: spec/support/matchers.rb

在这个文件中: spec/support/matchers.rb

You can implement this matcher...

你可以实现这个匹配器...

RSpec::Matchers.define :be_a_clone_of do |model1|
  match do |model2|
    ignored_columns = %w[id created_at updated_at]
    model1.attributes.except(*ignored_columns) == model2.attributes.except(*ignored_columns)
  end
end

After that, you can use it when writing a spec, by the following way...

之后,你可以在编写规范时使用它,通过以下方式......

item = create(:item) # FactoryBot gem
item2 = item.dup

expect(item).to be_a_clone_of(item2)
# True

Useful links:

有用的链接:

https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcherhttps://github.com/thoughtbot/factory_bot

https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher https://github.com/thoughtbot/factory_bot