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.
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)