【问题标题】:Removing one of the many duplicate entries in a habtm relationship?删除 habtm 关系中的许多重复条目之一?
【发布时间】:2009-07-08 00:16:11
【问题描述】:

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

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

那么……真的是把整个东西吹走,然后用钥匙重建它吗?

【问题讨论】:

    标签: ruby-on-rails ruby associations


    【解决方案1】:

    你真的应该在这里使用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
    

    请记住,DELETE 语句不支持 LIMIT 子句,因此如果表中没有某种其他标识符,确实无法在 SQL 中完成您想要的操作。

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

    【讨论】:

    • 我深信不疑!我不知道 DELETE 不支持 LIMIT (并且还没有非常好奇地挖掘 SQL 知识手册)。谢谢!
    【解决方案2】:

    关系必须是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
    

    ...假设您在StonePlacement 上有一个count 字段可以递增和递减。

    【讨论】:

    • 嗯,不...我想它不是必须的。我当然考虑过你的解决方法......最终,我宁愿在实际物体周围移动而不是保留计数器。喜欢你的照片。
    • 如果您想这样做,也可以使用StonePlacement.find_by_bowl_and_stone(@bowl, @stone).first.destroy 或类似的方式。
    • 当然可以...有一个中间对象来操作打开了各种可能性。那么,我的问题的答案是“否”吗?
    • 我不认为努力在连接表中拥有多个相同的行(即不可能知道你将要对哪一行进行操作)是对你时间的一种很好的利用。如果您想在碗中添加和移除石头,没有什么能阻止您向碗对象添加 add_stone 或 remove_stone 方法。
    • 很明显你是对的 - 有一个中间表显然是要走的路。然而,似乎必须有一种方法来做我最初想做的事情。
    【解决方案3】:

    怎么样

    bowl.stones.slice!(0)
    

    【讨论】:

    • >> @bowl.stones.count => 5 >> @bowl.stones.slice!(0) => # >> @bowl.stones.count => 5 # slice 不是 habtm 方法,但我确实有点希望。
    • 啊,对不起。不过值得一试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-18
    • 1970-01-01
    相关资源
    最近更新 更多