sites
sites

Reputation: 21795

Get parent when using accepts_nested_attributes_for

When receiving:

{
   parent:{
      children_attributes:[
        {child1}, {child2}
      ]
   }
}

And child1 instance is about to be created, I don't have parent_id set. I suppose this is set in save time.

How can I handle this to say something like:

class Child < ActiveRecord::Base
  belongs_to :parent
  before_save :do_something

  def do_something
    # access parent here
    # I can't, since parent_id is nil yet
  end
end

 Update to be more clear

class Parent <  ActiveRecord::Base
  has_many :children
  accepts_nested_attributes_for :children
end

Update 2

I tried this from related questions:

class Parent <  ActiveRecord::Base
  has_many :children, inverse_of: :parent
end

But same issue.

Update 3 according comment

I tried this

class Parent
  before_save :save_children
  attr_accessor :children_attributes

  def save_children
    children_attributes.all? do |k, attrs|
      # tried different things here, this one is the one I expected to work the most 
      child = Child.new attrs.merge(parent_id: id)
      child.save
    end
  end

I added parent_id to child attr_accessible call.

What am I missing?

Upvotes: 3

Views: 2470

Answers (5)

heratyian
heratyian

Reputation: 414

I think the best way to handle this is to override the child_attributes(attributes) method added to the parent model.

https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

You can set the parent association on the child here

def child_attributes(attributes)
  self.child = Child.new(attributes.merge(parent: self))
end

This allows you to reference the parent from the child on new

Upvotes: 0

Eric B
Eric B

Reputation: 321

The cause of this problem for me was the validation of the presence of the parent in the child class.

I essentially removed this line from the Child object and it works.

validates :parent, presence: true

From what I've found, however, the correct way to resolve this is the use of the inverse_of parameter. If you add this to the has_many of the parent and the belongs_to of the child, you should be good to go.

In the parent:

has_many :child, inverse_of: :parent

In the child:

belongs_to :parent, inverse_of: :child

Upvotes: 2

Paweł Dedio
Paweł Dedio

Reputation: 1358

Set inverse_of: :model_name in has_many declaration in your model where model_name is your 'parent' model.

For example: has_many :product_items, dependent: :destroy, inverse_of: :product

Upvotes: 1

Ross Joo
Ross Joo

Reputation: 180

This may or may not help with your issue: http://guides.rubyonrails.org/association_basics.html#association-callbacks

Association callbacks don't work with belongs_to, but does work well with has_many.

class Parent < ActiveRecord::Base
  has_many :children, before_add: :do_something

  def do_something(child)
    # self.id = 1
    # child.parent_id = 1
  end
end

# parent1 = Parent.find(1)
# parent1.children.create(...)

Upvotes: 0

Josh
Josh

Reputation: 530

In your model, you should be able to reference the parent like this:

self.parent_class

So for example, if a product have many parts, in your parts model (part.rb) you can refer to an attribute of the product like this:

self.product.id

That would return the id of the product that is the parent of the part. You can obviously reference any attribute of the parent like this.

This should work in your before_save callback in the child model.

Also, you shouldn't need the "inverse_of: :parent" that you included in your code above for this to work.

And to answer your question about when the parent id is added, that should be automatically passed in the params from your form to your controller if your form is set up correctly.

Upvotes: 0

Related Questions