Jeremy Thomas
Jeremy Thomas

Reputation: 6674

Rails 4: Access variable inside before_destroy callback

I have a recipe portal and these recipes can have tags.

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

class Tag < ActiveRecord::Base
    has_many :taggings, dependent: :destroy
    has_many :recipes, through: :taggings
end

class Tagging < ActiveRecord::Base
  belongs_to :tag
  belongs_to :recipe
end

...when I delete a recipe, I'd like to delete the tag if the recipe being deleted is the only recipe with this tag.

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

    before_destroy :remove_tags

    private

    # I need to pass an individual recipe
    def remove_tags
        if self.tags.present?
            self.tags.each do |tag|
                Recipe.tagged_with(tag).length == 1 ? tag.delete : next
                # tagged_with() returns recipes with the given tag name
            end
        end
    end
end

This function would work but I can't access the tags. How do I access the tags for the recipe being deleted?

Upvotes: 1

Views: 268

Answers (1)

epergo
epergo

Reputation: 575

You are accessing the tags of the recipe but you are not seeing anything becase dependant_destroy is executed before the actual destroy of the Recipe object.

If you inspect carefully the queries launched you will see that right before your callback, the DELETE FROM "taggings" . . . is executed, so when you try to access the tags of the Recipe it returns an empty array.

Because you don't want to destroy tags everytime you destroy a recipe but only when is the only one you should remove your dependant_destroy and place the logic in a after_destroy so the resulting code would be:

class Recipe < ApplicationRecord
  has_many :taggings
  has_many :tags, through: :taggings

  after_destroy :remove_tags

  private

  # I need to pass an individual recipe
  def remove_tags
    if self.tags.present?
      self.tags.each do |tag|
        Recipe.tagged_with(tag).length == 1 ? tag.delete : next
        # tagged_with() returns recipes with the given tag name
      end
    end
  end
end

Upvotes: 2

Related Questions