Comment tester l’égalité d’object (ActiveRecord)

Dans Ruby 1.9.2 sur Rails 3.0.3 , je tente de tester l’égalité des objects entre deux objects Friend (class hérite d’ ActiveRecord::Base ).

Les objects sont égaux, mais le test échoue:

 Failure/Error: Friend.new(name: 'Bob').should eql(Friend.new(name: 'Bob')) expected # got # (compared using eql?) 

Juste pour les sourires, je teste aussi l’identité de l’object, ce qui échoue car je m’attendais à:

 Failure/Error: Friend.new(name: 'Bob').should equal(Friend.new(name: 'Bob')) expected # => # got # => # 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. 

Quelqu’un peut-il m’expliquer pourquoi le premier test pour l’égalité des objects échoue et comment je peux affirmer que ces deux objects sont égaux?

Rails délègue délibérément les contrôles d’égalité à la colonne d’identité. Si vous voulez savoir si deux objects AR contiennent les mêmes éléments, comparez le résultat de l’appel de #atsortingbutes sur les deux.

Jetez un coup d’œil aux docs de l’ API sur l’opération == (alias eql? ) Pour ActiveRecord::Base

Renvoie true si comparison_object est le même object exact, ou comparaison_object est du même type et self a un ID et il est égal à comparison_object.id.

Notez que les nouveaux enregistrements diffèrent des autres enregistrements par définition , à moins que l’autre enregistrement ne soit le récepteur lui-même. De plus, si vous récupérez des enregistrements existants avec select et laissez l’ID, vous êtes seul, ce prédicat retournera faux.

Notez également que la destruction d’un enregistrement conserve son ID dans l’instance du modèle, de sorte que les modèles supprimés sont toujours comparables.

Si vous souhaitez comparer deux instances de modèle en fonction de leurs atsortingbuts, vous souhaiterez probablement exclure certains atsortingbuts non pertinents de votre comparaison, tels que: id , created_at et updated_at . (Je considère que ceux-ci sont plus de métadonnées à propos de l’enregistrement qu’une partie des données de l’enregistrement lui-même.)

Cela n’a peut-être aucune importance lorsque vous comparez deux nouveaux enregistrements (non enregistrés) (puisque id , created_at et updated_at seront tous nil jusqu’à ce qu’ils soient enregistrés), mais je trouve parfois nécessaire de comparer un object enregistré avec un object non enregistré (auquel cas = = vous donnerait faux puisque nul! = 5). Ou je veux comparer deux objects enregistrés pour savoir s’ils contiennent les mêmes données (donc, l’opérateur ActiveRecord == ne fonctionne pas, car il retourne false s’ils ont des id différents, même s’ils sont identiques).

Ma solution à ce problème consiste à append quelque chose comme ceci dans les modèles que vous souhaitez comparer en utilisant les atsortingbuts:

  def self.atsortingbutes_to_ignore_when_comparing [:id, :created_at, :updated_at] end def identical?(other) self. atsortingbutes.except(*self.class.atsortingbutes_to_ignore_when_comparing.map(&:to_s)) == other.atsortingbutes.except(*self.class.atsortingbutes_to_ignore_when_comparing.map(&:to_s)) end 

Ensuite, dans mes spécifications, je peux écrire des choses aussi lisibles et succinctes que celles-ci:

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

Je active_record_atsortingbutes_equality joyau active_record_atsortingbutes_equality et de le modifier pour utiliser ce comportement afin de pouvoir le réutiliser plus facilement.

Certaines questions que j’ai, cependant, incluent:

  • Un tel bijou existe-t-il déjà?
  • Comment appeler la méthode? Je ne pense pas que remplacer l’opérateur == existant est une bonne idée, alors pour l’instant je l’appelle identical? . Mais peut-être quelque chose comme practically_identical? ou atsortingbutes_eql? serait plus précis, car il ne vérifie pas si elles sont ssortingctement identiques ( certains atsortingbuts sont autorisés à être différents.) …
  • atsortingbutes_to_ignore_when_comparing est trop verbeux. Ce n’est pas que cela doit être explicitement ajouté à chaque modèle s’ils veulent utiliser les valeurs par défaut de la gemme. Peut-être permettre à la valeur par défaut d’être remplacée par une macro de classe comme ignore_for_atsortingbutes_eql :last_signed_in_at, :updated_at

Les commentaires sont les bienvenus …

Mise à jour : Au lieu de active_record_atsortingbutes_equality , j’ai écrit un tout nouveau bijou, active_record_ignored_atsortingbutes , disponible à l’ adresse http://github.com/TylerRick/active_record_ignored_atsortingbutes et http://rubygems.org/gems/active_record_ignored_atsortingbutes.

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