防止连接表为空

时间:2012-03-29 20:14:56

标签: ruby-on-rails activerecord

关系

class Promotion < ActiveRecord::Base
  has_many :promotion_sweepstakes,

  has_many :sweepstakes,
    :through => :promotion_sweepstakes
  end

class PromotionSweepstake < ActiveRecord::Base
  belongs_to :promotion
  belongs_to :sweepstake
end

class Sweepstake < ActiveRecord::Base
  # Not relevant in this question, but I included the class
end

所以通过联盟表PromotionSweepstake促销有很多抽奖活动。这是一个遗留的数据库模式,所以命名可能看起来有点奇怪,并且有一些self.table_name ==和foreign_key的东西被遗漏了。

此应用的性质要求,对于promotionId,联接表中至少有一个条目,因为没有抽奖活动会破坏应用。

第一个问题

我如何保证PromotionSweepstake中始终有一个条目可用于促销?创建时必须至少包含一个Sweepstake.id,一旦创建了连接表中的条目,必须至少为每个Promotion / promotion_id 一个。

第二个问题(其他选项)

如果之前的建议无法实现,我怀疑是否属实,那么可以通过另一种方式解决问题。有一种具有特定id的“默认Sweepstake”。如果通过表单将删除所有sweepstake_ids(以便删除连接表中促销的所有条目),我可以在PromotionSweepstake中创建新条目吗?

pseudo_code(排序)

使用ids [1,4,5]删除promotion_sweepstake,其中promotion_id = 1 如果id = 1的促销没有promotion_sweepstakes   使用promotion_id 1和sweepstake_id 100添加promotion_sweepstake 端

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

在创建和修改促销活动时,状态验证应解决问题。

class Promotion < ActiveRecord::Base
  has_many :promotion_sweepstakes
  has_many :sweepstakes,
    :through => :promotion_sweepstakes
  validates :sweepstakes, :presence => true
end

为了确保在尝试删除或更新Sweepstake或PromotionSweepstake时保持一致性,您必须为这两个类编写自己的验证。他们必须检查先前引用的促销是否仍然有效,即仍然有一些抽奖活动。

一个简单的解决方案将在validates :sweepstakes, :presence => true中占据Promotion的优势。在事务中更新引用的PromotionSweepstakes或Sweepstakes之后,您必须在之前引用的Promotions上调用Promotion#valid?。如果它们无效,则会回滚事务,因为修改会破坏一致性。

或者,您可以在PromotionSweepstake和Sweepstake中使用before_destroy,以防止更改违反您的一致性要求。

class PromotionSweepstake < ActiveRecord::Base
  belongs_to :promotion
  belongs_to :sweepstake
  before_destroy :check_for_promotion_on_destroy

private

  def check_for_promotion_on_destroy
    raise 'deleting the last sweepstake' if promotion.sweepstakes.count == 1
  end
end

class Sweepstake < ActiveRecord::Base
  has_many :promotion_sweepstakes
  has_many :promotions, :through => :promotion_sweepstakes
  before_destroy :check_for_promotions_on_destroy

private

  def check_for_promotions_on_destroy
    promotions.each do |prom|
      raise 'deleting the last sweepstake' if prom.sweepstakes.count == 1
    end
  end

end