70 Stimmen

has_and_belongs_to_many, Vermeidung von Duplikaten in der Join-Tabelle

Ich habe einen ziemlich einfachen HABTM-Satz von Modellen

class Tag < ActiveRecord::Base 
   has_and_belongs_to_many :posts
end 

class Post < ActiveRecord::Base 
   has_and_belongs_to_many :tags

   def tags= (tag_list) 
      self.tags.clear 
      tag_list.strip.split(' ').each do 
        self.tags.build(:name => tag) 
      end
   end 
end 

Jetzt funktioniert alles einwandfrei, außer dass ich eine Menge Duplikate in der Tabelle Tags erhalte.

Was muss ich tun, um Duplikate (basierend auf dem Namen) in der Tabelle "Tags" zu vermeiden?

73voto

Jeremy Lynch Punkte 5873

Verhindern von Duplikaten nur in der Ansicht (Faule Lösung)

Die folgenden nicht verhindern, dass doppelte Beziehungen in die Datenbank geschrieben werden, es wird lediglich sichergestellt find Methoden ignorieren Duplikate.

In Rails 5:

has_and_belongs_to_many :tags, -> { distinct }

Anmerkung: Relation#uniq wurde in Rails 5 abgeschrieben ( übergeben. )

In Rails 4

has_and_belongs_to_many :tags, -> { uniq }

Verhindern von Duplikaten Daten nicht gespeichert werden (beste Lösung)

Option 1: Verhindern Sie Duplikate vom Controller:

post.tags << tag unless post.tags.include?(tag)

Allerdings könnten mehrere Benutzer versuchen post.tags.include?(tag) zur gleichen Zeit, so dass dies den Bedingungen des Wettlaufs unterliegt. Dies wird erörtert aquí .

Aus Gründen der Robustheit können Sie dies auch in das Post-Modell (post.rb) aufnehmen

def tag=(tag)
  tags << tag unless tags.include?(tag)
end

Option 2: Einen eindeutigen Index erstellen

Die narrensicherste Methode zur Vermeidung von Duplikaten ist es, doppelte Beschränkungen auf der Datenbankebene zu haben. Dies kann erreicht werden, indem man eine unique index auf dem Tisch selbst.

rails g migration add_index_to_posts
# migration file
add_index :posts_tags, [:post_id, :tag_id], :unique => true
add_index :posts_tags, :tag_id

Sobald Sie den eindeutigen Index haben, führt der Versuch, einen doppelten Datensatz hinzuzufügen, zu einer ActiveRecord::RecordNotUnique Fehler. Dies zu behandeln, würde den Rahmen dieser Frage sprengen. Ansicht diese SO-Frage .

rescue_from ActiveRecord::RecordNotUnique, :with => :some_method

25voto

spyle Punkte 1870

Zusätzlich zu den oben genannten Vorschlägen:

  1. hinzufügen. :uniq zum has_and_belongs_to_many Verein
  2. Hinzufügen eines eindeutigen Index für die Join-Tabelle

Ich würde eine explizite Prüfung durchführen, um festzustellen, ob die Beziehung bereits besteht. Zum Beispiel:

post = Post.find(1)
tag = Tag.find(2)
post.tags << tag unless post.tags.include?(tag)

20voto

Simone Carletti Punkte 168374

Sie können die :uniq Option als in der Dokumentation beschrieben . Beachten Sie auch, dass die :uniq verhindert nicht die Erstellung doppelter Beziehungen, sondern stellt nur sicher, dass die Zugriffs-/Findmethoden sie nur einmal auswählen.

Wenn Sie Duplikate in der Zuordnungstabelle verhindern wollen, sollten Sie einen eindeutigen Index erstellen und die Ausnahme behandeln. Auch validates_uniqueness_of funktioniert nicht wie erwartet, da zwischen dem Zeitpunkt, an dem die erste Anfrage auf Duplikate prüft und in die Datenbank schreibt, eine zweite Anfrage in die Datenbank geschrieben werden kann.

20voto

cyrilchampier Punkte 2137

In Rails4:

class Post < ActiveRecord::Base 
  has_and_belongs_to_many :tags, -> { uniq }

(Achtung, die -> { uniq } muss direkt nach dem Beziehungsnamen stehen, vor anderen Parametern)

Rails-Dokumentation

12voto

Joshua Cheek Punkte 28392

Setzen Sie die Option uniq:

class Tag < ActiveRecord::Base 
   has_and_belongs_to_many :posts , :uniq => true
end 

class Post < ActiveRecord::Base 
   has_and_belongs_to_many :tags , :uniq => true

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X