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?

5voto

Jeff Whitmire Punkte 760

Ich würde es vorziehen, das Modell anzupassen und die Klassen auf diese Weise zu erstellen:

class Tag < ActiveRecord::Base 
   has_many :taggings
   has_many :posts, :through => :taggings
end 

class Post < ActiveRecord::Base 
   has_many :taggings
   has_many :tags, :through => :taggings
end

class Tagging < ActiveRecord::Base 
   belongs_to :tag
   belongs_to :post
end

Dann würde ich die Erstellung in Logik verpacken, so dass Tag-Modelle wiederverwendet werden, wenn sie bereits existieren. Ich würde wahrscheinlich sogar eine eindeutige Einschränkung auf den Tag-Namen setzen, um dies zu erzwingen. Das macht die Suche in beide Richtungen effizienter, da man einfach die Indizes der Join-Tabelle verwenden kann (um alle Beiträge für einen bestimmten Tag und alle Tags für einen bestimmten Beitrag zu finden).

Der einzige Haken ist, dass Sie die Umbenennung von Tags nicht zulassen können, da eine Änderung des Tag-Namens alle Verwendungen dieses Tags betreffen würde. Lassen Sie den Benutzer den Tag löschen und stattdessen einen neuen erstellen.

4voto

Sam Saffron Punkte 124121

Ich habe dies umgangen, indem ich einen before_save-Filter erstellt habe, der das Problem behebt.

class Post < ActiveRecord::Base 
   has_and_belongs_to_many :tags
   before_save :fix_tags

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

    def fix_tags
      if self.tags.loaded?
        new_tags = [] 
        self.tags.each do |tag|
          if existing = Tag.find_by_name(tag.name) 
            new_tags << existing
          else 
            new_tags << tag
          end   
        end

        self.tags = new_tags 
      end
    end

end

Es könnte leicht optimiert werden, um mit den Tags in Stapeln zu arbeiten, und es könnte auch eine etwas bessere Transaktionsunterstützung benötigt werden.

3voto

Für mich arbeiten

  1. Hinzufügen eines eindeutigen Index für die Join-Tabelle
  2. Überschreiben << Methode in der Beziehung

    has_and_belongs_to_many :groups do
      def << (group)
        group -= self if group.respond_to?(:to_a)
        super group unless include?(group)
      end
    end

2voto

Javeed Punkte 89

Das ist zwar schon alt, aber ich dachte, ich erzähle Ihnen mal, wie ich das mache.

class Tag < ActiveRecord::Base 
    has_and_belongs_to_many :posts
end 

class Post < ActiveRecord::Base 
    has_and_belongs_to_many :tags
end

In dem Code, in dem ich Tags zu einem Beitrag hinzufügen muss, mache ich etwas wie:

new_tag = Tag.find_by(name: 'cool')
post.tag_ids = (post.tag_ids + [new_tag.id]).uniq

Dies hat den Effekt, dass die Tags bei Bedarf automatisch hinzugefügt/entfernt werden oder dass nichts getan wird, wenn dies der Fall ist.

1voto

dav1dhunt Punkte 179

Extrahieren Sie den Tag-Namen zur Sicherheit. Prüfen Sie, ob das Tag in Ihrer Tag-Tabelle vorhanden ist, und erstellen Sie es, falls nicht:

name = params[:tag][:name]
@new_tag = Tag.where(name: name).first_or_create

Prüfen Sie dann, ob sie in dieser speziellen Sammlung vorhanden ist, und verschieben Sie sie, wenn dies nicht der Fall ist:

@taggable.tags << @new_tag unless @taggable.tags.exists?(@new_tag)

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