Suppose that Student
belongs_to :contact
and Teacher
also belongs_to :contact
. Suppose that first_name
is in Contact
, and Student
and Teacher
have a first name through the Contact
and has accepts_nested_attributes_for :contact
.
In an after_commit
callback, if first_name
changes, I want to see if it came from Student
or from Teacher
. I'm not sure how to do this though, or whether it's even possible. Inside of Student
's after_commit
, previous_changes
doesn't have first_name
. It's only in Contact
's after_commit
that it shows up. So inside of Contact
's after_commit
I want to see if it originated from Student
or from Teacher
.
How can I do that? Is there something similar to destroyed_by_association
? When I search for "by_association" in the docs I don't see anything else, nor do I see anything else in Active Record Autosave Association.
Note: It is possible for a Contact
to have both a Student
and a Teacher
, so it isn't as simple as just saying that if the Contact
doesn't have a Student
it must have came from Teacher
.
CodePudding user response:
If you add corresponding associations to Contact
they will be accessible in after_commit
callback:
class Student < ApplicationRecord
belongs_to :contact
accepts_nested_attributes_for :contact
end
class Teacher < ApplicationRecord
belongs_to :contact
accepts_nested_attributes_for :contact
end
class Contact < ApplicationRecord
has_one :teacher
has_one :student
after_commit do
p teacher || student
end
end
>> Teacher.create(contact_attributes: { name: 'teacher' })
...
# after_commit START
#<Teacher id: 1, contact_id: 1>
# after_commit END
=> #<Teacher:0x00007fd28056a9f0 id: 1, contact_id: 1>
One caveat is that if teacher is not present it will hit the database:
>> Student.create(contact_attributes: { name: 'student' });
...
# after_commit START
Teacher Load (0.4ms) SELECT "teachers".* FROM "teachers" WHERE "teachers"."contact_id" = $1 LIMIT $2 [["contact_id", 2], ["LIMIT", 1]]
#<Student id: 1, contact_id: 2>
# after_commit END
=> #<Student:0x00007fd2837bd9c0 id: 1, contact_id: 2>
Switching to polymorphic relationship is also a solution:
class Student < ApplicationRecord
has_one :contact, as: :contactable
accepts_nested_attributes_for :contact
end
class Teacher < ApplicationRecord
has_one :contact, as: :contactable
accepts_nested_attributes_for :contact
end
# NOTE: to add plymorphic relationship add this to contact migration:
# t.references :contactable, polymorphic: true
class Contact < ApplicationRecord
belongs_to :contactable, polymorphic: true
after_commit do
p contactable
end
end
>> Student.create(contact_attributes: { name: 'student' })
...
# after_commit START
#<Student id: 1>
# after_commit END
=> #<Student:0x00007f4a85c0d1c8 id: 1>
>> Teacher.create(contact_attributes: { name: 'teacher' })
...
# after_commit START
#<Teacher id: 1>
# after_commit END
=> #<Teacher:0x00007f4a884e2a08 id: 1>
Update
You have access to everything if you put the callback in Student
and Teacher
, regardless of the above setup.
Polymorphic:
class Teacher < ApplicationRecord
has_one :contact, as: :contactable
accepts_nested_attributes_for :contact
after_commit do
p self
p contact
p contact.previous_changes
end
end
>> Teacher.create(contact_attributes: { name: 'teacher' })
...
# after_commit START
#<Teacher id: 1>
#<Contact id: 1, name: "teacher", contactable_type: "Teacher", contactable_id: 1>
{"id"=>[nil, 1], "name"=>[nil, "teacher"], "contactable_type"=>[nil, "Teacher"], "contactable_id"=>[nil, 1]}
# after_commit END
=> #<Teacher:0x00007fbf1bdd2db8 id: 1>
Belongs_to:
class Teacher < ApplicationRecord
belongs_to :contact
accepts_nested_attributes_for :contact
after_commit do
p self
p contact
p contact.previous_changes
end
end
>> Teacher.create(contact_attributes: { name: 'teacher' })
...
# after_commit START
#<Teacher id: 1, contact_id: 1>
#<Contact id: 1, name: "teacher">
{"id"=>[nil, 1], "name"=>[nil, "teacher"]}
# after_commit END
=> #<Teacher:0x00007f9f2ce6a730 id: 1, contact_id: 1>
If you must have after_commit
in Contact
you have to explicitly tell Contact
that update came from Teacher
or Student
:
class Teacher < ApplicationRecord
belongs_to :contact
accepts_nested_attributes_for :contact
# NOTE: before saving, let contact know it's about to be updated
# by Teacher.
before_save do
contact.updated_from = :teacher
end
end
class Contact < ApplicationRecord
# NOTE: just a temporary attribute
attr_accessor :updated_from
after_commit do
print 'Updated from: '
p self.updated_from
end
end
>> Teacher.create(contact_attributes: { name: 'teacher' })
...
# after_commit START
Updated from: :teacher
# after_commit END
=> #<Teacher:0x00007fd6a40459c0 id: 1, contact_id: 1>
Or without before_save
callback in Teacher:
>> Teacher.create(contact_attributes: { name: 'teacher', updated_from: 'teacher' })
...
Updated from: "teacher"