Ответ 1
Rails сознательно делегирует проверки на равенство в столбец идентичности. Если вы хотите знать, содержат ли два объекта AR один и тот же материал, сравните результат вызова # атрибутов на обоих.
В Ruby 1.9.2
на 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?)
Просто для усмешек, я также проверяю идентификатор объекта, который терпит неудачу, как я ожидал:
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.
Может кто-нибудь объяснить мне, почему первый тест на равенство объектов терпит неудачу, и как я могу успешно утверждать, что эти два объекта равны?
Rails сознательно делегирует проверки на равенство в столбец идентичности. Если вы хотите знать, содержат ли два объекта AR один и тот же материал, сравните результат вызова # атрибутов на обоих.
Взгляните на API-документы в ==
(псевдоним eql?
) для ActiveRecord::Base
Возвращает true, если файл сравнения - это тот же самый точный объект, или объект сравнения_события того же типа, а self имеет идентификатор, и он равен compare_object.id.
Обратите внимание, что новые записи отличаются от любой другой записи по определению, если только другая запись не является самой ресиверкой. Кроме того, если вы выберете существующие записи с помощью select и оставьте ID, вы сами по себе, этот предикат вернет false.
Обратите внимание, что уничтожение записи сохраняет свой идентификатор в экземпляре модели, поэтому удаленные модели по-прежнему сопоставимы.
Если вы хотите сравнить два экземпляра модели на основе их атрибутов, вы, вероятно, захотите исключить некоторые несоответствующие атрибуты из вашего сравнения, например: id
, created_at
и updated_at
. (Я бы подумал, что это больше метаданных о записи, чем сама часть данных записи.)
Это может быть неважно, когда вы сравниваете две новые (несохраненные) записи (поскольку id
, created_at
и updated_at
будут nil
до сохранены), но иногда я считаю необходимым сравнить сохраненный объект с несохраненным (в этом случае == даст вам значение false с нуля = 5). Или я хочу сравнить два сохраненных объекта, чтобы выяснить, содержат ли они одни и те же данные (поэтому оператор ActiveRecord ==
не работает, поскольку он возвращает false, если у них разные id
, даже если они в противном случае идентичны).
Мое решение этой проблемы - добавить что-то подобное в модели, которые вы хотите сопоставить с помощью атрибутов:
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
и изменить его, чтобы использовать это поведение, чтобы его можно было более легко использовать повторно.
Некоторые вопросы, которые у меня есть, включают:
==
- хорошая идея, поэтому на данный момент я называю его identical?
. Но может быть, что-то вроде practically_identical?
или attributes_eql?
было бы более точным, так как оно не проверяет, строго ли они идентичны (некоторым атрибутам разрешено быть разными).attributes_to_ignore_when_comparing
слишком многословный. Не то, чтобы это нужно было явно добавлять к каждой модели, если они хотят использовать настройки по умолчанию. Возможно, разрешить переопределение по умолчанию макросом класса, например ignore_for_attributes_eql :last_signed_in_at, :updated_at
Комментарии приветствуются...
Обновить. Вместо того, чтобы развернуть active_record_attributes_equality
, я написал совершенно новый камень, active_record_ignored_attributes, доступный по адресу 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