views:

133

answers:

3

I'm modeling "featuring" based on my plan in this question and have hit a bit of a stumbling block.

I'm defining a Song's primary and featured artists like this:

  has_many :primary_artists, :through => :performances, :source => :artist,
           :conditions => "performances.role = 'primary'"

  has_many :featured_artists, :through => :performances, :source => :artist,
           :conditions => "performances.role = 'featured'"

This works fine, except when I'm creating a new song, and I give it a primary_artist via new_song.performances.build(:artist => some_artist, :role => 'primary'), new_song.primary_artists doesn't work (since the performance I created isn't yet saved in the database).

What's the best approach here? I'm thinking of going with something like:

has_many :artists, :through => :performances

  def primary_artists
    performances.find_all{|p| p.role == 'primary'}.map(&:artist)
  end
A: 

There's not much that you can do about the association not being recognized until you save. Arguably, it doesn't really exist until you save, validations pass, and the relevant transaction(s) are completed.

Regarding your question of cleaning up your primary_artist method, you could model it something like this.

class Song < ActiveRecord::Base
  has_many :performances
  has_many :artists, :through => :performances
  has_one :primary_artist, :through => :performances, :conditions => ["performances.roll = ?", "primary"], :source => :artist
end

It's unclear if you want one or many primary artists, but you can easily switch that has_one to has_many as needed.

jdl
But then how do I ensure, on song creation, that the song has exactly one primary artist?
Horace Loeb
That doesn't seem like a reasonable thing to check for. The way that you have this modeled, a song can have multiple performances. The 2nd performance would imply a 2nd primary artist. A song should not always have exactly one primary artist.
jdl
Also, I think you have this "featuring" thing backwards. That should be the special case via a roll. Everyone else can just be an "artist." You're going to run into trouble with your primary artists as soon as you try to create a performance object for a duet.
jdl
A: 

You've nailed the source of your problem with build vs. create.

As for finding the primary artist of a song. I would add a named_scope on artist to select only featured/primary artists.

class Artist < ActiveRecord::Base
  ...
  named\_scope :primary, :joins => :performances, :conditions => "performances.role = primary"
  named\_scope :featured, :joins => :performances, :conditions => "performances.role = featured"
end

To get the primary artist for a song, you would do @song.artists.primary or if you prefer your primary_artists method in song.

def primary_artists
  artists.primary
end

However, after looking at your initial question, I think your database layout is insufficient. It's workable but not clear, I've posted my suggestions there, where it belongs.

These named scopes I would also work under my proposed scheme as well.

EmFi
+1  A: 

I think you're overcomplicating it. Just because things have similarities doesn't mean you should put them all in the same box.

class Song < ActiveRecord::Base

  has_one :artist  # This is your 'primary' artist
  has_and_belongs_to_many :featured_artists, :source => :artist  # And here you make a featured_artists_songs table for the simple HABTM join

  validates_presence_of :artist
end

Poof, no more confusion. You still have to add song.artist before you can save, but that's what you wanted. Right?

SFEley