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

发布于 2024-10-13 12:22:28 字数 1125 浏览 1 评论 0原文

Rails 3.0.3 上的 Ruby 1.9.2 中,我尝试测试两个 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?)

只是为了笑,我还测试了对象身份,这正如我所期望的那样失败了:

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.

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

笑红尘 2024-10-20 12:22:28

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

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.

臻嫒无言 2024-10-20 12:22:28

查看 上的 API 文档 ==(别名eql?)用于ActiveRecord::Base的操作

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

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

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

Take a look at the API docs on the == (alias eql?) operation for 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.

随心而道 2024-10-20 12:22:28

如果您想根据属性比较两个模型实例,您可能需要从比较中排除某些不相关的属性,例如:idcreated_at< /code> 和 updated_at。 (我认为这些更多的是关于记录的元数据,而不是记录数据本身的一部分。)

当您比较两个新的(未保存的)记录时,这可能并不重要(因为id、created_atupdated_at 在保存之前都将是 nil),但有时我发现有必要比较已保存的< /em> 对象有一个未保存(在这种情况下 == 会给你 false,因为 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< /code> gem 并将其更改为使用此行为,以便可以更轻松地重用它。

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

  • 这样的宝石是否已经存在?
  • 该方法应该被称为什么?我认为覆盖现有的 == 运算符不是一个好主意,所以现在我将其称为 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

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, and updated_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, and updated_at will all be nil 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 different id'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:

  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_equality gem and changing it to use this behavior so that this can be more easily reused.

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 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 like 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_attributes and http://rubygems.org/gems/active_record_ignored_attributes

冷了相思 2024-10-20 12:22:28
 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
 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
メ斷腸人バ 2024-10-20 12:22:28

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

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

您可以实现此匹配器...

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

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

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

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

有用的链接:

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

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

Inside this file:
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-matcher
https://github.com/thoughtbot/factory_bot

眉目亦如画i 2024-10-20 12:22:28

如果像我一样,您正在寻找这个问题的Minitest答案,那么这里有一个自定义方法,它断言两个对象的属性是相等的。

它假定您始终希望排除 idcreated_atupdated_at 属性,但如果您愿意,可以覆盖该行为。

我喜欢保持 test_helper.rb 干净,因此创建了一个包含以下内容的 test/shared/custom_assertions.rb 文件。

module CustomAssertions
  def assert_attributes_equal(original, new, except: %i[id created_at updated_at])
    extractor = proc { |record| record.attributes.with_indifferent_access.except(*except) }
    assert_equal extractor.call(original), extractor.call(new)
  end
end

然后更改您的 test_helper.rb 以包含它,以便您可以在测试中访问它。

require 'shared/custom_assertions'

class ActiveSupport::TestCase
  include CustomAssertions
end

基本用法:

test 'comments should be equal' do
  assert_attributes_equal(Comment.first, Comment.second)
end

如果要覆盖它忽略的属性,请使用 except 参数传递字符串或符号数组:

test 'comments should be equal' do
  assert_attributes_equal(
    Comment.first, 
    Comment.second, 
    except: %i[id created_at updated_at edited_at]
  )
end

If like me you're looking for a Minitest answer to this question then here's a custom method that asserts that the attributes of two objects are equal.

It assumes that you always want to exclude the id, created_at, and updated_at attributes, but you can override that behaviour if you wish.

I like to keep my test_helper.rb clean so created a test/shared/custom_assertions.rb file with the following content.

module CustomAssertions
  def assert_attributes_equal(original, new, except: %i[id created_at updated_at])
    extractor = proc { |record| record.attributes.with_indifferent_access.except(*except) }
    assert_equal extractor.call(original), extractor.call(new)
  end
end

Then alter your test_helper.rb to include it so you can access it within your tests.

require 'shared/custom_assertions'

class ActiveSupport::TestCase
  include CustomAssertions
end

Basic usage:

test 'comments should be equal' do
  assert_attributes_equal(Comment.first, Comment.second)
end

If you want to override the attributes it ignores then pass an array of strings or symbols with the except arg:

test 'comments should be equal' do
  assert_attributes_equal(
    Comment.first, 
    Comment.second, 
    except: %i[id created_at updated_at edited_at]
  )
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文