I have a Card model that has many CardSets and a CardSet model that has many Cards through a Membership model:
class Card < ActiveRecord::Base
has_many :memberships
has_many :card_sets, :through => :memberships
end
class Membership < ActiveRecord::Base
belongs_to :card
belongs_to :card_set
validates_uniqueness_of :card_id, :scope => :card_set_id
end
class CardSet < 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 < CardSet
end
class GameSet < CardSet
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 < CardSet
validate :validates_uniqueness_of_cards
def validates_uniqueness_of_cards
membership = Membership.find(
:first,
:include => :card_set,
:conditions => [
"card_id IN (?) AND card_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! Thanks J.!
Update 2
I spoke a little too soon. The above solution was working great until I went to update an Expansion with a new card. It was incorrectly identifying subsequent #valid?
checks as false because it was finding itself in the database. I fixed this by adding a check for #new_record?
in the validation method:
class Expansion < CardSet
validate :validates_uniqueness_of_cards
def validates_uniqueness_of_cards
sql_string = "card_id IN (?) AND card_sets.type = ?"
sql_params = [self.cards.map(&:id), "Expansion"]
unless new_record?
sql_string << " AND card_set_id <> ?"
sql_params << self.id
end
membership = Membership.find(
:first,
:include => :card_set,
:conditions => [sql_string, *sql_params]
)
errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil?
end