Reputation: 773
So I have some models set up that can each have a comment. I have it set up using has_many_polymorphs, but I'm starting to run into some issues where it's not working how I think it should.
For example:
class Project < ActiveRecord::Base
end
class Message < ActiveRecord::Base
has_many_polymorphs :consumers,
:from => [:projects, :messages],
:through => :message_consumers,
:as => :comment # Self-referential associations have to rename the non-polymorphic key
end
class MessageConsumer < ActiveRecord::Base
# Self-referential associations have to rename the non-polymorphic key
belongs_to :comment, :foreign_key => 'comment_id', :class_name => 'Message'
belongs_to :consumer, :polymorphic => true
end
In this case, the Message wouldn't get deleted when the Project is removed, because the Message is really the parent in the relationship.
I simplified it a little for the example, but there are other models that have have a Message, and there are also Attachments that work similarly.
What would be the correct way to set this up so that the children get removed when the parent is deleted? I'm hoping to not have a million tables, but I can't quite figure out another way to do this.
Upvotes: 1
Views: 575
Reputation: 66721
When you say "so that the children get removed when the parent is deleted?", can you give an example? I.e. when a project is deleted I want all its messages to be deleted too? What happens when you delete a message, do you want anything else (e.g. all corresponding message_consumer entries) to be deleted as well?
UPDATE
OK, so has_many_polymorphs
will automatically delete "orphaned" message_consumer
s. Your problem is that a message may have more than one consumer, so deleting a project may not be sufficient grounds for deleting all its associated messages (as other consumers may depend on those messages.)
In this particular case you can set up an after_destroy
callback in MessageConsumer
, to check whether there still exist other MessageConsumer
mappings (other than self) that reference the Message
and, if none exist, also delete the message, e.g.:
class MessageConsumer < ActiveRecord::Base
...
after_destroy :delete_orphaned_messages
def delete_orphaned_messages
if MessageConsumer.find(:first, :conditions => [ 'comment_id = ?', self.comment_id] ).empty?
self.comment.delete
end
end
end
All this is happening inside a transaction, so either all deletes succeed or none succeed.
You should only be aware of potential race conditions whereby one session would arrive at the conclusion that a Message
is no longer used, whereas another one may be in the process of creating a new MessageConsumer
for that exact same Message
. This can be enforced by referential integrity at the DB level (add foreign key constraints, which will make on of the two sessions fail, and will keep your database in a consistent state), or locking (ugh!)
Upvotes: 1