Tom Lehman
Tom Lehman

Reputation: 89203

has_many :through issue with new records

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

Upvotes: 1

Views: 291

Answers (3)

SFEley
SFEley

Reputation: 7766

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?

Upvotes: 1

EmFi
EmFi

Reputation: 23450

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.

Upvotes: 0

jdl
jdl

Reputation: 17790

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.

Upvotes: 0

Related Questions