在habtm关系中删除许多重复条目之一?

时间:2009-07-08 00:16:12

标签: ruby-on-rails ruby associations

为了讨论的目的,我用两张桌子做了一个测试:

: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

这些模型非常不言自明,基本,但在这里它们是:

class Stone < ActiveRecord::Base

  has_and_belongs_to_many :bowls

end

class Bowl < ActiveRecord::Base

  has_and_belongs_to_many :stones

end

现在,问题是:我希望每个碗里都有许多相同的石头。我希望能够只删除一个,留下其他相同的石头。这看起来非常基本,而且我真的希望我能找到一个解决方案而且当我这么做时不会觉得自己太过白痴。

这是一个测试运行:

@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?

所以...正在吹走整个事物,并从键中重建它真的是唯一的方法吗?

3 个答案:

答案 0 :(得分:1)

关系必须是habtm吗?

你可以有这样的东西......

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

...假设count上的StonePlacement字段增加和减少。

答案 1 :(得分:0)

怎么样

bowl.stones.slice!(0)

答案 2 :(得分:0)

你应该在这里使用has_many :through关系。否则,是的,实现目标的唯一方法是创建一种方法来计算特定石头的当前数量,将其全部删除,然后再添加N - 1石头。

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

请记住LIMIT语句中不支持DELETE子句,因此如果表中没有其他类型的标识符,就无法在SQL中完成所需的操作。

(MySQL实际上支持DELETE ... LIMIT 1,但AFAIK ActiveRecord不会为您执行此操作。您需要执行原始SQL。)