2

Existe-t-il un moyen facile ou au moins élégant d'empêcher les doublons dans polymorphique has_many à travers les associations? J'ai deux modèles, des histoires et des liens qui peuvent être étiquetés. Je prends la décision consciente de ne pas utiliser un plugin ici. Je veux vraiment comprendre tout ce qui se passe et ne pas dépendre du code de quelqu'un d'autre que je ne comprends pas complètement.Rails empêchant les doublons dans polymorphique has_many: through les associations

Pour voir ce que ma question est en venir, si j'exécutez la commande suivante dans la console (en supposant que les objets de l'histoire et de balises existent dans la base de données déjà)

s = Story.find_by_id(1) 

t = Tag.find_by_id(1) 

s.tags << t 

s.tags << t 

Mes marquages ​​table de jointure aura deux entrées ajoutées à lui, chacun avec les mêmes données exactes (tag_id = 1, taggable_id = 1, taggable_type = "Histoire"). Cela ne me semble pas très approprié. Ainsi, dans une tentative pour empêcher que cela se produise, j'ajouté ce qui suit à mon modèle de marquage:

before_validation :validate_uniqueness 

def validate_uniqueness 
    taggings = Tagging.find(:all, :conditions => { :tag_id => self.tag_id, :taggable_id => self.taggable_id, :taggable_type => self.taggable_type }) 

    if !taggings.empty? 
     return false 
    end 

    return true 
end 

Et il fonctionne presque comme prévu, mais si je tente d'ajouter une étiquette en double à une histoire ou d'un lien, je reçois un ActiveRecord :: RecordInvalid: Échec de l'exception de validation. Il semble que lorsque vous ajoutez une association à une liste, elle appelle la sauvegarde! (plutôt que de sauvegarder sans!) méthode qui soulève des exceptions si quelque chose se passe mal plutôt que de simplement retourner false. Ce n'est pas tout à fait ce que je veux arriver. Je suppose que je peux entourer toutes les tentatives d'ajouter de nouveaux tags avec un try/catch, mais cela va à l'encontre de l'idée que vous ne devriez pas attendre vos exceptions et c'est quelque chose que je m'attends à arriver.

Existe-t-il une meilleure façon de faire cela qui ne soulèvera pas d'exceptions alors que tout ce que je veux faire est simplement de ne pas sauvegarder l'objet dans la base de données parce qu'il existe un doublon?

Répondre

1

Vous pourriez le faire de plusieurs façons.

Définir une méthode personnalisée add_tags qui charge toutes les balises existantes puis vérifie et ajoute uniquement les nouvelles.

Exemple:

def add_tags *new_tags 
    new_tags = new_tags.first if tags[0].kind_of? Enumerable #deal with Array as first argument 
    new_tags.delete_if do |new_tag| 
    self.tags.any? {|tag| tag.name == new_tag.name} 
    end 
    self.tags += new_tags 
end 

Vous pouvez également utiliser un filtre before_save pour faire en sorte que la liste des balises n'a pas de doublons. Cela entraînerait un peu plus de frais généraux, car cela se produirait à chaque sauvegarde.

+0

C'est à peu près exactement ce que je veux. Cependant, je ne sais pas comment vous allez sur la surcharge de la balise << méthode. J'ai essayé de copier la méthode add_tags et de la renommer simplement "tags <<" mais elle me donne NoMethodError: méthode non définie 'tags 'pour # chaque fois que j'essaie de l'appeler. J'aimerais vraiment que la méthode << soit surchargée car il semble plus naturel de l'utiliser. – seaneshbaugh

+0

Oh ouais ... j'ai oublié, vous devez définir une méthode tags qui renvoie un objet qui a une méthode '' '. MAIS, self.tags << new_tags ne fonctionnera pas de toute façon, car cela va juste essayer d'ajouter un tableau de tags en tant qu'élément dans la collection de tags. Vous voudriez utiliser 'self.tags + = new_tags' pour cette ligne. Réponse mise à jour –

1

Vous pouvez définir l'option uniq lors de la définition de has_many relation. Rails API docs dit:

:uniq

If true, duplicates will be omitted from the collection. Useful in conjunction with :through.

(Extrait de: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001833 sous "Options prises en charge" sous-position)

+0

L'option uniq n'empêche en fait rien d'être placé dans la base de données. En fait, d'après mes tests, il semble que cela ne joue que lorsqu'un objet est instancié en ce qu'il semble appeler uniq! sur la liste d'association de l'objet qui les supprime juste en mémoire à ce moment. Si des associations en double sont ajoutées, elles apparaissent toujours dans la liste de l'objet et dans la base de données. Ceci est un exemple parfait de la raison pour laquelle la documentation sur les rails est horrible et a désespérément besoin de clarification et/ou de révision. – seaneshbaugh

+3

': uniq' n'a rien à voir avec la prévention des entrées dupliquées dans la base de données ... –

0

Je crois que cela fonctionne ...

class Tagging < ActiveRecord::Base 
    validate :validate_uniqueness 

    def validate_uniqueness 
     taggings = Tagging.find(:all, :conditions => { 
     :tag_id => self.tag_id, 
     :taggable_id => self.taggable_id, 
     :taggable_type => self.taggable_type }) 

     errors.add_to_base("Your error message") unless taggings.empty? 
    end 
end 

Laissez-moi savoir si vous avez des erreurs ou quelque chose avec cela:]

Questions connexes