0

I was trying to implement a first_or_build method and I encounter a problem when saving my parent : the children were missing.

Everything is working fine when I call my method on the relation like parent.childs.first_or_build(name: 'Foo'); parent.save! whereas nothing happen when I do parent.childs.where(name: 'Foo').first_or_build; parent.save!.

The main objective was to propose a similar behavior than .first_or_create applied to the result of a query for example. (Don't tell me about .first_or_initialize !)

Any idea?

Examples :

# this is not working :(
2.times { |i| parent.childs.where(name: "child #{i}").build { |c| c.age = 42 } } ; parent.childs  
=> #<ActiveRecord::Associations::CollectionProxy []>

# while this is
2.times { |i| parent.childs.build { |c| c.name = "#{child #{i}"; c.age = 42 } } ; parent.childs  
=> #<ActiveRecord::Associations::CollectionProxy [#<Child name: "child 0", age: 42>, #<Child name: "child 1", age: 42>]>
Community
  • 1
  • 1
jgburet
  • 535
  • 3
  • 15

1 Answers1

0

Sorry, I don't quit understand the part about first_or_build method, so I will just talk about the examples there.

First of all, we know that parent.childs.where(name: "child #{i}") and parent.childs are in different class

    parent.children.where(name: "child").class
    #=>  Child::ActiveRecord_AssociationRelation

    parent.children.class
    #=>  Child::ActiveRecord_Associations_CollectionProxy

so it's clear why their :build method are different, the doc are here

ActiveRecord_Associations_CollectionProxy

ActiveRecord_AssociationRelation

I will try to express my view here. When you use ActiveRecord_AssociationRelation to build a new child, it will initialize a new Child object, and set its parent_id, but it is just an Child object. In this time, when you execute parent.children, the result is empty.

parent.children.where(name: "child1").build({age: 1})
#=> <Child id: nil, name: "child1", age: 1, parent_id: 1, created_at: nil, updated_at: nil>
parent.children
#=> <ActiveRecord::Associations::CollectionProxy []>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy []>

But when you use ActiveRecord_Associations_CollectionProxy, it will initialize a new Child object, and it will also attach itself to parent, so then when you execute parent.children, the result is not empty.

parent.children.build({name: "child2", age: 2})
#=> <Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil
parent.children
#=>  <ActiveRecord::Associations::CollectionProxy [#<Child id: nil, name: "child2", age: 2, parent_id: 1, created_at: nil, updated_at: nil>]>
parent.save #=> true
parent.children.reload
#=> <ActiveRecord::Associations::CollectionProxy [#<Child id: 3, name: "child2", age: 2, parent_id: 1, created_at: "2015-05-28 17:02:39", updated_at: "2015-05-28 17:02:39">]>

In the second way, parent know it has children, so when it save, it will save its children.I think this is it.

ShallmentMo
  • 449
  • 1
  • 4
  • 15
  • Of course there is two classes in this example. I don't get why, while both methods instantiate new `Child` objects, only the first allow the save of the new `children` when saving the `parent`. In both cases, when printing new children, you can see they contains the id of their parent. – jgburet May 28 '15 at 09:05
  • they are all just new Child object even though the contains the id of their parent, they haven't been writen to database. But it the second way, the parent object will know it has child, so when parent save, it will save its children at the same time. – ShallmentMo May 28 '15 at 17:21