Подтвердить что ты не робот

Как проверить соответствие объекта (ActiveRecord)

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

Может кто-нибудь объяснить мне, почему первый тест на равенство объектов терпит неудачу, и как я могу успешно утверждать, что эти два объекта равны?

4b9b3361

Ответ 1

Rails сознательно делегирует проверки на равенство в столбец идентичности. Если вы хотите знать, содержат ли два объекта AR один и тот же материал, сравните результат вызова # атрибутов на обоих.

Ответ 2

Взгляните на API-документы в == (псевдоним eql?) для ActiveRecord::Base

Возвращает true, если файл сравнения - это тот же самый точный объект, или объект сравнения_события того же типа, а self имеет идентификатор, и он равен compare_object.id.

Обратите внимание, что новые записи отличаются от любой другой записи по определению, если только другая запись не является самой ресиверкой. Кроме того, если вы выберете существующие записи с помощью select и оставьте ID, вы сами по себе, этот предикат вернет false.

Обратите внимание, что уничтожение записи сохраняет свой идентификатор в экземпляре модели, поэтому удаленные модели по-прежнему сопоставимы.

Ответ 3

Если вы хотите сравнить два экземпляра модели на основе их атрибутов, вы, вероятно, захотите исключить некоторые несоответствующие атрибуты из вашего сравнения, например: 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

Ответ 4

 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