2009-07-08 13 views
1

Für die Zwecke der Diskussion kochte ich einen Test mit zwei Tabellen auf:Entfernen eines der vielen doppelten Einträge in einer Beziehung-Beziehung?

:stones and :bowls (both created with just timestamps - trivial) 

create_table :bowls_stones, :id => false do |t| 
    t.integer :bowl_id, :null => false 
    t.integer :stone_id, :null => false 
end 

Die Modelle sind ziemlich selbsterklärend und einfach, aber hier sind sie:

class Stone < ActiveRecord::Base 

    has_and_belongs_to_many :bowls 

end 

class Bowl < ActiveRecord::Base 

    has_and_belongs_to_many :stones 

end 

nun die Problem ist: Ich möchte, dass es in jeder Schale viele gleiche Steine ​​gibt. Und ich möchte in der Lage sein, nur einen zu entfernen und die anderen identischen Steine ​​zurückzulassen. Das scheint ziemlich einfach zu sein, und ich hoffe wirklich, dass ich beide eine Lösung finden kann und mich nicht wie ein Idiot fühle, wenn ich es tue.

Hier ist ein Testlauf:

@stone = Stone.new 
@stone.save 
@bowl = Bowl.new 
@bowl.save 

#test1 - .delete 
5.times do 
    @bowl.stones << @stone 
end 

@bowl.stones.count 
=> 5 
@bowl.stones.delete(@stone) 
@bowl.stones.count 
=> 0 
#removed them all! 

#test2 - .delete_at 
5.times do 
    @bowl.stones << @stone 
end 

@bowl.stones.count 
=> 5 
index = @bowl.stones.index(@stone) 
@bowl.stones.delete_at(index) 
@bowl.stones.count 
=> 5 
#not surprising, I guess... delete_at isn't part of habtm. Fails silently, though. 
@bowl.stones.clear 

#this is ridiculous, but... let's wipe it all out 
5.times do 
    @bowl.stones << @stone 
end 

@bowl.stones.count 
=> 5 
ids = @bowl.stone_ids 
index = ids.index(@stone.id) 
ids.delete_at(index) 
@bowl.stones.clear 
ids.each do |id| 
    @bowl.stones << Stone.find(id) 
end 
@bowl.stones.count 
=> 4 
#Is this really the only way? 

So ... ist das Ganze weggeblasen und es von Schlüssel wirklich der einzige Weg zu rekonstruieren?

Antwort

0

Sie sollten wirklich hier eine has_many :through Beziehung werden. Ansonsten, ja, der einzige Weg, um Ihr Ziel zu erreichen, ist eine Methode zu erstellen, um die aktuelle Zahl eines bestimmten Steins zu zählen, sie alle zu löschen und dann N - 1 Steine ​​zurück hinzuzufügen.

class Bowl << ActiveRecord::Base 
    has_and_belongs_to_many :stones 

    def remove_stone(stone, count = 1) 
    current_stones = self.stones.find(:all, :conditions => {:stone_id => stone.id}) 
    self.stones.delete(stone) 
    (current_stones.size - count).times { self.stones << stone } 
    end 
end 

Denken Sie daran, dass LIMIT Klauseln nicht in DELETE Aussagen unterstützt werden, so gibt es wirklich keine Möglichkeit, was Sie in SQL ohne irgendeine Art von anderer Kennung in der Tabelle wollen zu tun ist.

(MySQL tut tatsächlich Unterstützung DELETE ... LIMIT 1 aber AFAIK Active nicht das für Sie tun. Sie bräuchten rohen SQL auszuführen.)

+0

Ich bin überzeugt! Ich wusste nicht, dass LIMIT nicht von DELETE unterstützt wurde (und war noch nicht verzweifelt genug, um in den SQL-Lorebooks zu graben). Vielen Dank! –

1

Muss die Beziehung habtm sein?

Sie so etwas wie dieses haben könnte ...

class Stone < ActiveRecord::Base 
    has_many :stone_placements 
end 

class StonePlacement < ActiveRecord::Base 
    belongs_to :bowl 
    belongs_to :stone 
end 

class Bowl < ActiveRecord::Base 
    has_many :stone_placements 
    has_many :stones, :through => :stone_placements 

    def contents 
    self.stone_placements.collect{|p| [p.stone] * p.count }.flatten 
    end 

    def contents= contents 
    contents.sort!{|a, b| a.id <=> b.id} 
    contents.uniq.each{|stone| 
     count = (contents.rindex(stone) - contents.index(stone)) + 1 
     if self.stones.include?(stone) 
     placement = self.stone_placements.find(:first, :conditions => ["stone_id = ?", stone]) 
     if contents.include?(stone) 
      placement.count = count 
      placement.save! 
     else 
      placement.destroy! 
     end 
     else 
     self.stone_placements << StonePlacement.create(:stone => stone, :bowl => self, :count => count) 
     end 
    } 
    end 
end 

... vorausgesetzt, Sie haben einen count Feld auf StonePlacement und Abnahme zu erhöhen.

+0

Nun, nein ... Ich nehme an, es nicht zu sein braucht. Ich habe Ihre Problemumgehung sicherlich in Erwägung gezogen. Letztendlich würde ich mich eher mit realen Objekten bewegen als mit Zählern. Liebe dein Bild. –

+1

Sie könnten auch 'StonePlacement.find_by_bowl_and_stone (@bowl, @stone) .first.destroy' oder etwas Ähnliches machen, wenn Sie es so machen wollten. – wombleton

+1

Sicher ist sicher ... ein Zwischenobjekt zu manipulieren eröffnet alle möglichen Möglichkeiten. Ist die Antwort auf meine Frage dann ein "Nein"? –

0

Wie wäre es

bowl.stones.slice!(0) 
+0

>> @ bowl.stones.count => 5 >> @ bowl.stones.slice!(0) => # >> @ bowl.stones .count => 5 # Scheibe ist nicht eine Methode, aber ich habe meine Hoffnungen ein wenig erholt. –

+0

Ahh, Entschuldigung. Einen Versuch wert. –

Verwandte Themen