validate uniqueness amongst multiple subclasses with Single Table Inheritance

Posted by irkenInvader on Stack Overflow See other posts from Stack Overflow or by irkenInvader
Published on 2010-06-10T21:00:17Z Indexed on 2010/06/11 1:22 UTC
Read the original article Hit count: 197

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

© Stack Overflow or respective owner

Related posts about ruby-on-rails

Related posts about single-table-inheritance