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

class Contract
  belongs_to :employee

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:

  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)

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


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 =

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

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

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