Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails accepts_nested_attributes_for child doesn't have parent set when validating

I'm trying to access my parent model in my child model when validating. I found something about an inverse property on the has_one, but my Rails 2.3.5 doesn't recognize it, so it must have never made it into the release. I'm not sure if it's exactly what I need though.

I want to validate the child conditionally based on parent attributes. My Parent model has already been created. If the child hasn't been created when I update_attributes on the parent, then it doesn't have access to the parent. I'm wondering how I can access this parent. It should be easy, something like parent.build_child sets the parent_id of the child model, why is it not doing it when building the child for accepts_nested_attributes_for?

For Example:

class Parent < AR
  has_one :child
  accepts_nested_attributes_for :child
end
class Child < AR
  belongs_to :parent
  validates_presence_of :name, :if => :some_method

  def some_method
    return self.parent.some_condition   # => undefined method `some_condition' for nil:NilClass
  end
end

My form is standard:

<% form_for @parent do |f| %>
  <% f.fields_for :child do |c| %>
    <%= c.name %>
  <% end %>
<% end %>

With an update method

def update
  @parent = Parent.find(params[:id])
  @parent.update_attributes(params[:parent])   # => this is where my child validations take place
end
like image 562
brad Avatar asked Feb 09 '10 04:02

brad


3 Answers

I had basically the same problem with Rails 3.2. As suggested in the question, adding the inverse_of option to the parent's association fixed it for me.

Applied to your example:

class Parent < AR
  has_one :child, inverse_of: :parent
  accepts_nested_attributes_for :child
end

class Child < AR
  belongs_to :parent, inverse_of: :child
  validates_presence_of :name, :if => :some_method

  def some_method
    return self.parent.some_condition   # => undefined method `some_condition' for nil:NilClass
  end
end
like image 156
Henrik N Avatar answered Sep 29 '22 12:09

Henrik N


I had a similar problem: Ruby on Rails - nested attributes: How do I access the parent model from child model

This is how I solved it eventually; by setting parent on callback

class Parent < AR
  has_one :child, :before_add => :set_nest
  accepts_nested_attributes_for :child

private
  def set_nest(child)
    child.parent ||= self
  end
end
like image 32
TMaYaD Avatar answered Sep 29 '22 11:09

TMaYaD


You cannot do this because in-memory child doesn't know the parent its assigned to. It only knows after save. For example.

child = parent.build_child
parent.child # => child
child.parent # => nil

# BUT
child.parent = parent
child.parent # => parent
parent.child # => child

So you can kind of force this behavior by doing reverse association manually. For example

def child_with_inverse_assignment=(child)
  child.parent = self
  self.child_without_inverse_assignment = child
end

def build_child_with_inverse_assignment(*args)
  build_child_without_inverse_assignment(*args)
  child.parent = self
  child
end

def create_child_with_inverse_assignment(*args)
  create_child_without_inverse_assignment(*args)
  child.parent = self
  child
end

alias_method_chain :"child=", :inverse_assignment
alias_method_chain :build_child, :inverse_assignment
alias_method_chain :create_child, :inverse_assignment

If you really find it necessary.

P.S. The reason it's not doing it now is because it's not too easy. It needs to be explicitly told how to access parent/child in each particular case. A comprehensive approach with identity map would've solved it, but for newer version there's :inverse_of workaround. Some discussions like this one took place on newsgroups.

like image 39
Max Chernyak Avatar answered Sep 29 '22 13:09

Max Chernyak