Carl Edwards
Carl Edwards

Reputation: 14424

Creating custom polymorphic models in Rails

I was interested in creating a model that could stand alone but could also belong to another model. So for example: I have an Artist that has many Albums. In addition to having many tracks (which is irrelevant for this case) it also can have many Singles. Here's the catch. There are some instances where a single doesn't belong to an album and is just used for promo (aka a promo single). So I thought I'd approach it using a polymorphic association:

class Artist < ActiveRecord::Base
  has_many :albums
  has_many :singles, as: :singleable
end

class Album < ActiveRecord::Base
  belongs_to :artist
  has_many :singles, as: :singleable
end

class Single < ActiveRecord::Base
  belongs_to :singleable, polymorphic: true
end

Not being entirely familiar with polymorphic associations I didn't know if this would be the correct way to setup what I had in mind of doing. Alternatively should I have created an entirely separate model called PromoSingle or create a dropdown that would define the single type?

Upvotes: 0

Views: 729

Answers (1)

D-side
D-side

Reputation: 9485

I don't think this case actually needs a polymorphic association. It should work, yes, but you'll soon find yourself in situations when you need to perform complicated searches to get seemingly simple results.

Polymorphic association should be used when association is semantically the same, but may involve different objects. It's not the case here, an artist is the one who created the track. But the album isn't.

This can cause trouble at least if you ever decide to fetch specific artist's tracks. Here's what ActiveRecord would have to do with your structure (including internal operations):

  • Get list L1
    • Get an array A of album_ids, whose artist_id is X (a parameter)
    • Get all singles, whose singleable_type is "Album" and singleable_id is in the array of albums A, fetched before.
  • Get list L2
    • Get all singles, whose singleable_type is "Artist" and singleable_id is X.
  • Concatenate L1 and L2

Here is what I suggest you do.

class Artist < ActiveRecord::Base
  has_many :albums
  has_many :singles
end

class Album < ActiveRecord::Base
  belongs_to :artist
  has_many :singles
end

class Single < ActiveRecord::Base
  belongs_to :artist
  belongs_to :album
end

PromoSingles fit well here too. Just because association is defined doesn't mean it should be present: it only means "it might be present, there is a place where we can put it".

Should you absolutely need it to be present (not here, somewhere else), you'd use a validation to ensure.

Otherwise, you may have items that don't belong to anyone, or, technically, belong to nil (Ruby level) or NULL (DB level). It's not bad if it makes sense.

Upvotes: 1

Related Questions