Rails: How to create two records with has_one association at the same time

198 Views Asked by At

I have a Rails 6 app. The Hat model is in a has_one polymorphic relationship with the Person model. (I know this seems backwards. I'm not the author of this code.) The Person model creates the associated Hat in a callback. The problem is that the Hat needs to reference attributes of its Person during creation, and that association is nil when created in this way...

class Person < ApplicationRecord
  belongs_to :wearable, polymorphic: true, required: false, dependent: :destroy

  after_create do 
    if wearable.nil?
      wearable = Hat.create(...) # at this moment, the Hat has no Person
      self.wearable = wearable
      save
    end
  end

end


class Hat < ApplicationRecord
  has_one    :person, as: :wearable, class_name: 'Person'

  after_create do
    embroider( self.person.initials ) # <-- This will error!!
  end

end

Is there a way the Person can create the Hat with the association in place from the outset?

I think this is possible with non-polymorphic relationships by calling create on the association method. I think something like self.hat.create(...) would work, but I'm not sure how to do this in a polymorphic context.

1

There are 1 best solutions below

0
Alex On BEST ANSWER

When creating a hat you can set the relationship that you have defined, which is person:

after_create do
  Hat.create!(person: self) unless wearable
  # NOTE: don't need the rest
  # self.wearable = wearable
  # save
end

You must use create! to rollback a transaction on errors.


These don't work here:

build_wearable, create_wearable - these methods are not created for polymorphic relationships.

accepts_nested_attributes_for doesn't work on a polymorphic relationship.


Because these ^ don't work. You can add inverse_of option on :wearable and just assign new Hat to wearable:

# normally this will fail, which is what the current issue is
Person.new(wearable: Hat.new).wearable.person.initials

# if you add inverse_of
belongs_to :wearable,
  polymorphic: true,
  required:    false,
  dependent:   :destroy,
  inverse_of:  :person

# now it works
Person.new(wearable: Hat.new).wearable.person.initials

# this also avoids an extra "Person Update"

# `after_create` in `Person` can be taken out and you can create
# a person like this:
Person.create(wearable: Hat.new)