Note that there are some explanatory texts on larger screens.

plurals
  1. POvalidate uniqueness amongst multiple subclasses with Single Table Inheritance
    primarykey
    data
    text
    <p>I have a Card model that has many CardSets and a CardSet model that has many Cards through a Membership model:</p> <pre><code>class Card &lt; ActiveRecord::Base has_many :memberships has_many :card_sets, :through =&gt; :memberships end class Membership &lt; ActiveRecord::Base belongs_to :card belongs_to :card_set validates_uniqueness_of :card_id, :scope =&gt; :card_set_id end class CardSet &lt; ActiveRecord::Base has_many :memberships has_many :cards, :through =&gt; :memberships validates_presence_of :cards end </code></pre> <p>I also have some sub-classes of the above using Single Table Inheritance:</p> <pre><code>class FooCard &lt; Card end class BarCard &lt; Card end </code></pre> <p>and</p> <pre><code>class Expansion &lt; CardSet end class GameSet &lt; CardSet validates_size_of :cards, :is =&gt; 10 end </code></pre> <p>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:</p> <pre><code>some_cards = FooCard.all( :limit =&gt; 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** </code></pre> <p>However, GameSets should allow this behavior:</p> <pre><code>other_cards = FooCard.all( :limit =&gt; 10 ) first_set = GameSet.new second_set = GameSet.new first_set.cards = other_cards # Valid second_set.cards = other_cards # Also valid </code></pre> <p>I'm guessing that a validates_uniqueness_of call is needed somewhere, but I'm not sure where to put it. Any suggestions?</p> <h2>UPDATE 1</h2> <p>I modified the Expansion class as sugested:</p> <pre><code>class Expansion &lt; CardSet validate :validates_uniqueness_of_cards def validates_uniqueness_of_cards membership = Membership.find( :first, :include =&gt; :card_set, :conditions =&gt; [ "card_id IN (?) AND card_sets.type = ?", self.cards.map(&amp;:id), "Expansion" ] ) errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil? end end </code></pre> <p>This works! Thanks J.!</p> <h2>Update 2</h2> <p>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 <code>#valid?</code> checks as false because it was finding itself in the database. I fixed this by adding a check for <code>#new_record?</code> in the validation method:</p> <pre><code>class Expansion &lt; 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(&amp;:id), "Expansion"] unless new_record? sql_string &lt;&lt; " AND card_set_id &lt;&gt; ?" sql_params &lt;&lt; self.id end membership = Membership.find( :first, :include =&gt; :card_set, :conditions =&gt; [sql_string, *sql_params] ) errors.add_to_base("a Card can only belong to a single Expansion") unless membership.nil? end </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload