Suppose I have a Genre model that can be associated with either an instance of Book or Movie. Something like,
class Genre < ActiveRecord::Base
belongs_to :genreable, polymorphic: true
end
class Book < ActiveRecord::Base
has_many :genres, as: :genreable
end
class Movie < ActiveRecord::Base
has_many :genres, as: :genreable
end
Now, suppose I want to normalize my genre data, so each genre has only a single entry in the Genres table:
class Genre < ActiveRecord::Base
has_many :genre_instances
end
class GenreInstance < ActiveRecord::Base
belongs_to :genre
belongs_to :genreable, polymorphic: true
end
class Book < ActiveRecord::Base
has_many :genre_insances, as: :genreable
has_many :genres, through: :genre_instances
end
class Movie < ActiveRecord::Base
has_many :genre_insances, as: :genreable
has_many :genres, through: :genre_instances
end
My genre_instances table has fields for genre_id
, genreable_id
, and genreable_type
.
So, if genre 5 is "adventure" and movie 13 is "Point Break", I could have an entry in genre_instances
like genre_id: 5, genreable_id: 13, genreable_type: 'Movie'
.
And if "Tom Sawyer" is book 13, I might have another entry like genre_id: 5, genreable_id: 13, genreable_type: 'Book'
.
How can I enforce uniqueness in the genre_instances table in a way that accounts for the 'type' column? I want to ensure "Point Break" can only have a single "adventure" entry, without preventing "Tom Sawyer" from also having such an entry.
Edit: Thanks to cwsault for the answer below. It was dead-on. I also added a unique index at the database level with a migration like so:
class CreateGenreInstances < ActiveRecord::Migration
def change
create_table :genre_instances do |t|
t.references :genre
t.references :genreable, polymorphic: true
t.timestamps
end
add_index :genre_instances, [:genre_id, :genreable_id, :genreable_type],
unique: true, name: 'genre_and_genreable'
end
end
...and a side-note: I had to specify the name of the index because ActiveRecord's auto-generated index-name was over the 64-character limit.
Took it all for a test run and it's working just as expected.