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