validate uniqueness amongst multiple subclasses with Single Table Inheritance
- by irkenInvader
I have a Card model that has many Sets and a Set model that has many Cards through a Membership model:
class Card < ActiveRecord::Base
  has_many :memberships
  has_many :sets, :through => :memberships
end
class Membership < ActiveRecord::Base
  belongs_to :card
  belongs_to :set
  validates_uniqueness_of :card_id, :scope => :set_id
end
class Set < ActiveRecord::Base
  has_many :memberships
  has_many :cards, :through => :memberships
  validates_presence_of :cards
end
I also have some sub-classes of the above using Single Table Inheritance:
class FooCard < Card
end
class BarCard < Card
end
and
class Expansion < Set
end
class GameSet < Set
  validates_size_of :cards, :is => 10
end
All of the above is working as I intend.  What I'm trying to figure out is how to validate that a Card can only belong to a single Expansion.  I want the following to be invalid:
some_cards = FooCard.all( :limit => 25 )
first_expansion = Expansion.new
second_expansion = Expansion.new
first_expansion.cards = some_cards
second_expansion.cards = some_cards
first_expansion.save    # Valid
second_expansion.save   # **Should be invalid**
However, GameSets should allow this behavior:
other_cards = FooCard.all( :limit => 10 )
first_set = GameSet.new
second_set = GameSet.new
first_set.cards = other_cards    # Valid
second_set.cards = other_cards   # Also valid
I'm guessing that a validates_uniqueness_of call is needed somewhere, but I'm not sure where to put it.  Any suggestions?
UPDATE 1
I modified the Expansion class as sugested:
class Expansion < Set 
  validate :validates_uniqueness_of_cards
  def validates_uniqueness_of_cards
    membership = Membership.find(
      :first,
      :include => :set,
      :conditions => [
        "card_id IN (?) AND sets.type = ?",
        self.cards.map(&:id), "Expansion"
      ]
    )
    errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil?
  end
end
This works when creating initial expansions to validate that no current expansions contain the cards.  However, this (falsely) invalidates future updates to the expansion with new cards.  In other words:
old_exp = Expansion.find(1)
old_exp.card_ids                 # returns [1,2,3,4,5]
new_exp = Expansion.new
new_exp.card_ids = [6,7,8,9,10]
new_exp.save                     # returns true
new_exp.card_ids << [11,12]      # no other Expansion contains these cards
new_exp.valid?                   # returns false ... SHOULD be true