Don't validate belongs_to, or else...
Over on rails-core, I posted Edge Rails fails saving parent when has_many child ?.
The models I am using are:
app/models/invoice.rb
1 class Invoice < ActiveRecord::Base 2 belongs_to :customer 3 has_many :lines, :class_name => 'InvoiceItem' 4 validates_presence_of :no, :customer 5 end
app/models/invoice_item.rb
1 class InvoiceItem < ActiveRecord::Base 2 belongs_to :invoice 3 validates_presence_of :invoice 4 end
As is, it was impossible to use the normal build and save idiom:
1 $ ruby script\console 2 Loading development environment. 3 >> invoice = Invoice.new 4 => #<Invoice:0x3a76060 ...> 5 >> line = invoice.lines.build 6 => #<InvoiceItem:0x3a5d610 ...> 7 >> invoice.save! 8 ActiveRecord::RecordInvalid: Validation failed: Lines is invalid 9 from 10 ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/validations.rb:736:in 11 `save!' 12 from (irb):3 13 >> puts line.errors.full_messages 14 Invoice can't be blank 15
Well, of course. I know invoice can’t be blank. If I remove :invoice from the validates_presence_of, things work out fine:
1 ... 2 >> invoice.save! 3 => nil 4 >> invoice.new_record? 5 => false 6 >> line.new_record? 7 => false 8 >> line.invoice 9 => nil 10 >> # Huh?
Digging into the code, ActiveRecord::Associations::AssociationProxy#set_belongs_to_association_for it turns out that only the foreign key is assigned to the child instance, not the full parent model. The behavior as seen above is therefore “normal”.
Turns out that if I validate the foreign key instead, things work perfectly:
app/models/invoice_item.rb
1 class InvoiceItem < ActiveRecord::Base 2 belongs_to :invoice 3 validates_presence_of :invoice_id 4 end
1 ... 2 >> invoice.save! 3 => nil 4 >> line.new_record? 5 => false 6 >> line.invoice 7 => #<Invoice:0x39fbea8 ...>
Lesson learned: don’t validate the presence of the associated model, only it’s foreign key.