Bart Jedrocha
Bart Jedrocha

Reputation: 11570

Rails has_one :through. Building associated object

I have the following data model in my Rails 2.3 application

class PortraitSubject
  has_many    :portraits
  has_one     :primary_portrait, :through => :portraits, :source => :asset, :conditions => ['portraits.primary = ?', true]
  has_many    :supplementary_portraits, :through => :portraits, :source => :asset, :conditions => ['portraits.primary = ?', false]

  ...
end

class Portrait
  belongs_to :portrait_subject
  belongs_to :asset

  ...
end

I want to build the associated proxy models using Rails but trying to build primary_portrait fails with an exception. I.e.

# This works
subject = PortraitSubject.new
subject.supplementary_portraits.build
subject.save

# This doesn't
subject = PortraitSubject.new
subject.build_primary_portrait
# => NoMethodError: undefined method `build_primary_portrait' for #<PortraitSubject:0x007ff16fe38948>

I'm not sure what I'm doing wrong. Looking through the Rails guides it looks like this should be possible with a has_one relationship. Any help would be greatly appreciated.

Upvotes: 6

Views: 1400

Answers (3)

Ray Baxter
Ray Baxter

Reputation: 3200

You are going to go crazy with those naming conventions. A PrimaryPortrait and a SecondaryPortrait ought to be special cases of Portrait not the assets that belong to a Portrait. It's already breaking your design that you can't build one.

Try this:

class PortraitSubject
  has_many    :portraits
  has_one     :primary_portrait, :conditions => {:primary => true}
  has_many    :supplementary_portraits, :conditions => {:primary => false}

  has_many    :portrait_assests, :through => :portraits
  has_one     :primary_portrait_asset, :through => :primary_portrait
  has_many    :supplementary_portrait_assets, :through => :supplementary_portraits

end

then, if you need to build a primary_portait_asset write an instance method

def build_primary_portrait_asset
  primary_portrait || build_primary_portrait
  primary_portrait.asset || primary_portrait.build_asset
end

Upvotes: 1

DaveMongoose
DaveMongoose

Reputation: 905

I'd suggest splitting this into two associations:

class PortraitSubject
  has_many    :portraits
  has_one     :primary_portrait, :class_name => "Portrait", :conditions => ['portraits.primary = ?', true]
  has_one     :primary_portrait_asset, :through => :primary_portrait, :source => :asset

  has_many    :supplementary_portraits, :class_name => "Portrait", :conditions => ['portraits.primary = ?', false]
  has_many     :supplementary_portrait_assets, :through => :supplementary_portraits, :source => :asset

  ...
end

Then you can use subject.build_primary_portrait to create the portrait model, and access its asset through subject.primary_portrait_asset.

Upvotes: 0

rovermicrover
rovermicrover

Reputation: 1453

Why not do the following.

class Portrait
  belongs_to :portrait_subject
  belongs_to :asset

  ...
end

-

class PrimaryPortrait < Portrait
  ...
end

-

class SupplementaryPortraits < Portrait
  ...
end

-

class PortraitSubject
  has_one     :primary_portrait
  has_many    :supplementary_portraits
  ...
end

This follows rails design patterns more closely. You will have to add a type column though.

Upvotes: 0

Related Questions