Nested has_one object should be built automatically when nessecary

I am always surprised that nested objects have to built manually for accepts_nested_attributes_for to work. Let’s say I have an Employee model that has_one Contract:

class Employee
  has_one :contract
  accepts_nested_attributes_for :contract
end

class Contract
  belongs_to :employee
end

I would expect rails to automatically create the contract record, when its nested attributes are provided:

Let’s say I have this code in API controller:

employee.update({
  name: "Mike",
  contract_attributes: {
   start_date: "2020-02-02"
  }
})

But this doesn’t work. Instead I have to catch this somewhere, usually in assign_attributes:

class Employee
  def assign_attributes(attrs = {})
    self.build_contract if self.contract.blank? && attrs.include?(:contract_attributes)
    super(attrs)
  end
def

This is unnatural and seems unnecessary. Why couldn’t rails handle this automatically?

2 Likes

This also bites me sometimes. Our convention at the moment is to handle it in the controller, to help the form render.

def new
  @employee = Employee.new
  @employee.build_contract
end

def edit
  @employee = Employee.find(params[:id])
  @employee.build_contract unless @employee.contract # This is especially easy to forget
end

I can imagine that this might come up in other places than forms which warrants to override assign_attributes but so far it didn’t happen for us so that’s why it’s not in the model.

1 Like

Think this would be @employee.build_contract unless @employee.contract :+1:

1 Like

Well, all other model/data initializations happen usually in the model (for example in before_save callbacks), why this should be different?

Anyway, regardless of where you put it, this looks very much like a boilerplate code, that should be handled by Rails automatically.

You can just override contract method in model


def contract
  super || build_contract
end

That’s a nice way to make it work, although the “don’t touch schema fields in the model” approach is strong in Rails, so this feels almost like a sin.

I would still prefer Rails to handle this, though.

1 Like

This is not schema field. Also don’t touch schema fields in the model” approach is strong in Rails - first time hearing about it. All schema fields in rails are for example typecasted.

If you dont want override method you can have new name

def contract_or_build
  contract || build_contract
end