Validation performed on has_many associations before updating the belongs_to foreign key?

Hi,

I have a Campaign model which has_many :condition_sets. The ConditionSet belongs_to :campaign and also validates_presence_of :campaign_id

Now, If I…

c = Campaign.new
s = c.condition_sets.build

c.save!
ActiveRecord::RecordInvalid: Validation failed: Condition sets is invalid

s.errors.full_messages
=> [“Campaign can’t be blank”]

I’d expect Rails to set the campaign_id foreign key before performing validation on the ConditionSet, right?

Removing the validates_presence_of :campaign_id lets the campaign save…

c = Campaign.new
s = c.condition_sets.build
c.save!
s.campaign
=> #<Campaign id: 1, created_at: “2007-07-13 09:20:08”, updated_at: “2007-07-13 09:20:08”>

Am I just being dumb???

Ian Leitch wrote:

I have a Campaign model which has_many :condition_sets. The ConditionSet belongs_to :campaign and also validates_presence_of :campaign_id

Now, If I...

>> c = Campaign.new
>> s = c.condition_sets.build
>> c.save!
ActiveRecord::RecordInvalid: Validation failed: Condition sets is invalid
>> s.errors.full_messages
=> ["Campaign can't be blank"]

I'd expect Rails to set the campaign_id foreign key _before_ performing validation on the ConditionSet, right?

campaign_id can't be set before it's validated because it is unknown
until the campaign is saved, and validation is designed to prevent
anything being saved unless the whole object hierarchy is valid.

The usual solution is:

    class ConditionSet
      validates_presence_of :campaign # Checks for valid object or fk
    end
    c = Campaign.new
    s = c.condition_sets.build
    s.campaign = c # Currently not set automatically by Rails
    c.save!

Thanks Mark, it seems pretty obvious now you’ve pointed it out.

I’m having a similar problem with a has_one association this time but applying what I’ve learned here doesn’t seem to work. To confuse me even more, the parent has_one association works fine.

Here is some debug of what my app is doing…

BUILD Incentive::ConditionSet#build_promoter
ASSIGN Incentive::ConditionSet#promoter <= Incentive::PromoterCondition
ASSIGN FOREIGN Incentive::PromoterCondition#condition_set <= Incentive::ConditionSet

OK this is odd… or just not what I’m expecting.

class OrderValueCondition
belongs_to :order, :class_name => ‘Incentive::OrderCondition’
end

Rails should infer order_id as the foreign key, right? I’m not sure what Rail thinks it is, because when I explicitly specify :foreign_key => :order_id it saves.

…the penny drops…

Just noticed:

DEPRECATION WARNING: The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition. See http://www.rubyonrails.org/deprecation for details.

Doh!