Search code examples
ruby-on-railsruby-on-rails-4activerecordpolymorphic-associations

Detect relation changes on polymorphic association


I have 2 models that are linked through a polymorphic association

class MyModel < ActiveRecord::Base
  has_many :taggings, :as => :taggable
  has_many :tags, :through => :taggings

  def tags=(ids)
    self.tags.delete_all
    self.tags << Tag.where(id: ids)
  end
end

class Tagging < ActiveRecord::Base
  include PublishablePolymorphicRelationship
  belongs_to :tag
  belongs_to :taggable, :polymorphic => true
end


class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :my_models, :through => :taggings, :source => :taggable, :source_type => 'MyModel'
end

tag1 = Tag.create!(...)
tag2 = Tag.create!(...)
my_model = MyModel.create!(...)

my_model.update!(tags: [tag1.id])

I created a concern that implements the after_update hook so that I can publish the changes on a message queue

However, when the hook is invoked, the changes hash is empty. as well as for the relation

module PublishablePolymorphicRelationship
  extend ActiveSupport::Concern
  included do
    after_update    :publish_update

    def publish_update
      model = self.taggable
      puts model.changes
      puts self.changes
      ... # do some message queue publish code
    end
  end

end This would return

{}
{}

Is there a way I can catch the changes for the polymorphic associations. Ideally, I would not refer directly to the tags model in the concern because I want this concern to be reusable for other models. I am open to adding bits of configuration in the model using the concern though.

Follow up question: Is this the right way to do this? I am surprised that the update hook is invoked in the first place. Perhaps I should act on either the creation or deletion hooks instead? I am open to suggestions.


Solution

  • It will never work as you think - taggings is just a join model. Rows are only really inserted/deleted indirectly when you add/remove tags to an item. And when that happens there are no changes on either end of the association.

    Thus unless you actually manually update the tagging and either end of the associations then publish_update will return en empty hash.

    If you want to create a resuable component that notifies you when a m2m association is created/destroyed you would do it like so:

    module Trackable
    
      included do
        after_create :publish_create!
        after_destroy :publish_destroy!
      end
    
      def publish_create!
        puts "#{ taxonomy.name } was added to #{item_name.singular} #{ item.id }"
      end
    
      def publish_destroy!
        puts "#{ taxonomy.name } was removed from #{item_name.singular} #{ item.id }"
      end
    
      def taxonomy_name
        @taxonomy_name || = taxonomy.class.model_name
      end
    
      def item_name
        @item_name || = item.class.model_name
      end
    end
    
    class Tagging < ActiveRecord::Base
      include PublishablePolymorphicRelationship
      belongs_to :tag
      belongs_to :taggable, polymorphic: true
    
      alias_attribute :item, :taggable
      alias_attribute :taxonomy, :tag
    end
    
    class Categorization < ActiveRecord::Base
      include PublishablePolymorphicRelationship
      belongs_to :category
      belongs_to :item, polymorphic: true
    
      alias_attribute :item, :taggable
      alias_attribute :taxonomy, :tag
    end
    

    Otherwise you need to apply the tracking callbacks to the actual classes you are interested in the changes in.