I am trying to write a method that would appl开发者_如何学Goy directly to several models with HABTM relations to clean up any unused relations.
def cleanup self.all.each do |f| if f.videos.count == 0 self.destroy(f) end end end
Where do I save this method to and is this even the correct syntax for such a method? It would theoretically be run as:
>>Tag.cleanup
Write external module and include it in each Model you need
Sadly people keep on using has_and_belongs_to_many
even though it leads to all kinds of orphans like this. A has_many ..., :through
relationship can be flagged :dependent => :destroy
to clean up unused children automatically. It's common that you'll have unused join records and they are obnoxious to remove.
What you might do is approach this from a SQL angle since has_and_belongs_to_many
records are inaccessible if their parent records are no longer defined. They simply do not exist as far as ActiveRecord is concerned. Using a join model means you can always access this data since they are issued their own ids.
has_and_belongs_to_many
relationships are based on a compound key which makes removing them a serious nuisance. Normally you'd do a DELETE FROM table WHERE id IN (...) AND ...
and be confident that only the target records are removed. With a compound key you can't do this.
You may find this works for an example Tag to Item relationship:
DELETE FROM item_tags, tags, items WHERE item_tags.tag_id=tags.id AND item_tags.item_id=items.id AND tags.id IS NULL AND items.id IS NULL
The DELETE
statement can be really particular about how it operates and does not give the same latitude as a SELECT
with joins that can be defined as left or right, inner or outer as required.
If you had a primary ID key in your join table you could do it easily:
DELETE FROM item_tags WHERE id IN (SELECT id FROM item_tags LEFT JOIN tags ON item_tags.tag_id=tags.id LEFT JOIN items ON item_tags.item_id=items.id WHERE tags.id IS NULL AND items.id IS NULL)
In fact, it might be advantageous to add a primary key to your relationship table even if ActiveRecord ignores it.
Edit:
As for your module issue, if you're stuck with that approach:
module CleanupMethods
def cleanup
# ...
end
end
class Tag
# Import the module methods as class methods
extend CleanupMethods
end
If you use a counter cache column you can do this a lot more easily, but you will also have to ensure your counter caches are accurate.
You want to add a class method to the Tag class, and instead of iterating through all the tag objects (requiring rails to load each one) and then checking for videos through Active Record, it's faster to load all the orphaned records using a query and then destroy only those.
Guessing that you have tags and videos, here, and that tag_videos is your join table, in Rails 2.x you might write
def self.cleanup
find(:all, :conditions => "id NOT IN (select tag_id from tag_videos)").destroy_all
end
In Rails 3 you'd write
def self.cleanup
where("id NOT IN (select tag_id from tag_videos)").destroy_all
end
精彩评论