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
self.hat.create(...)
will not work since a belongs_to
assocation is nil until you assign it. Instead the singular assocation macros generate new_wearable
, create_wearable
etc methods:
class Person < ApplicationRecord
belongs_to :wearable, polymorphic: true, required: false, dependent: :destroy
after_create do
self.create_wearable(type: 'Hat', ...) if wearable.nil?
end
end
But general the entire approach using an after_create
callback is far from ideal.
You're creating two different transactions instead of one and you're not getting the data integrity gaurentee provided by wrapping both in the same transaction:
person = Person.new(...) do |p|
p.new_wearable(...)
end
person.save # saves both in a single transaction
If either insert fails here you get a rollback instead of leaving the data in an incomplete state.
To add to this are the general drawbacks with callbacks which is that they rely on implicit logic and it can be hard to actually control when and where they are fired. Chances are its better to just expliticly create the assocatiated record in the one place you actually need it (the create method in the controller).
Rails also provides accepts_nested_attributes
that lets you pass the attributes for the nested record without the extra work:
class Person < ApplicationRecord
# ...
accepts_nested_attributes_for :wearable
end
person = Person.new(..., wearable_attributes: { type: 'Hat', ... })
person.save # saves both in a single transaction
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.