seenmycorpseanywhere
seenmycorpseanywhere

Reputation: 758

rails: deleting objects in has_many through only if no longer referenced

A posts model:

class Post < ActiveRecord::Base
  has_many :taggings, dependent: :destroy
  has_many :tags, through: :taggings

and a tags model:

class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :posts, through: :taggings

and the associated join table:

class Tagging < ActiveRecord::Base
  belongs_to :tag, dependent: :destroy
  belongs_to :post

so, the dependent: :destroy in Post makes the db entries in Tagging to be destroyed when doing for instance Post.last.destroy, and the dependent: :destroy in Tagging makes the associated Tag object to be destroyed as well. Fine.

My problem is, 2 posts may share the same Tag, and I would like to destroy that tag -only- if the post being deleted is the last one referencing it. I don't allow duplicate entries in the Tag table. The tags for a post are submitted as a a string with the tags separated by commas, and upon creation of a post I do the following:

  def tag_names=(names)
    self.tags = names.split(",").map{ |tag| Tag.where(name: tag.squish).first_or_create! }
  end

Any idea on how I could achieve this? Thanks!

Upvotes: 0

Views: 741

Answers (1)

Fer
Fer

Reputation: 3347

Are you using a plugin? act_as_taggable or something like that?

If so, take a look to the docs because probably is already implemented.

If not, you can always implement the after_destroy callback to count the elements associated to a tag and delete the tag in the case that you have "empty" tags.

For instance, in your Post model:

class Post

  before_destroy :clean_up_tags

  protected

  def clean_up_tags
    tags_to_delete = Tagging.where(id: self.tag_ids).group(:tag_id).having("count(distinct taggable_id) = 1").pluck(:id)
    Tag.find(tags_to_delete).map(&:destroy)
  end

end

This method is assuming that you have a method tag_ids that returns the tags associated to an specific Post and that your taggings model is polymorphic).

As you probably have more than one model with the tags feature, a good approach would be packing this method into a module and include it in all the models, in this way you keep the things DRY.

Upvotes: 1

Related Questions