views:

358

answers:

2

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.

+1  A: 

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

Cheers, V.

vladr
Yes, when a project is deleted, the message should be deleted as well. Any orphan message_consumer entries should be cleaned up too.
Karl
A: 

You could simplify this a lot by using acts_as_commentable.

Sarah Mei
Message is just one example, there are a couple of other classes in the project that do the same thing. I'm not sure it really fits into what acts_as_commentable is solving.
Karl