Rails 5.2 callbacks to trigger score recalculation for relationships I have a model that is in a HABTM relationship with 2 other models:

Activity.rb

  has_and_belongs_to_many :mandates, -&gt; { distinct }
  has_and_belongs_to_many :contacts, -&gt; { distinct }


I had to add the following callbacks, since the models defined above have a 'score algorithm' that must be recomputed if activities are removed completely, or one is created initially:

  after_commit  :increase_contact_scores, :increase_mandate_scores
  after_destroy :decrease_contact_scores, :decrease_mandate_scores


The callbacks code seems horribly the same. Is there a way to simplify this by somehow passing the HABT relation class from the callback to a more 'generic' method that can replace these?

I also need to figure out how to ONLY trigger the relevant callback based on the type of the HABTM (ie. not call both increases and both decreases, since if a mandate is destroyed, calling the decrease_contacts_score method will fail).

  def increase_contact_scores
    contacts.each do |contact|
      if contact.activities.count == 1
        contact.calculate_score
        contact.save!
      end
    end
  end

  def increase_mandate_scores
    mandates.each do |mandate|
      if mandate.activities.count == 1
        mandate.calculate_score
        mandate.save!
      end
    end
  end

  def decrease_contact_scores
    contacts.each do |contact|
      if mandate.activities.count.zero?
        contact.calculate_score
        contact.save!
      end
    end
  end

  def decrease_mandate_scores
    mandates.each do |mandate|
      if mandate.activities.count.zero?
        mandate.calculate_score
        mandate.save!
      end
    end
  end
In testing this code it turned out there is a chicken-and-egg situation, and more specific requirements. 

In order to know which object (Contact or Mandate) to update the score for, the Activity callbacks need more 'knowledge':

eg. 2 cases:

1 Contact A has 0 activities ->

if an activity is created, recalculate the score for A

2 Contact A has > 0 activities ->

if an activity is created, no need to recalculate the score

if an activity is deleted, recalculate the score only if activities = 0

The same is true for Mandate.

So, I ended up with a cleaner solution, with drier code:

Activity.rb

  before_commit :mark_objects_for_rescoring, on: :create
  before_destroy :mark_objects_for_rescoring
  after_commit :rescore_objects, on: [:create, :destroy]

  after_initialize do
    self.contacts_to_recalculate = []
  end

  private

  def mark_objects_for_rescoring
    contacts.each do |contact|
      contacts_to_recalculate &lt;&lt; contact.id if contact.activities.count == 1
    end
    mandates.each do |mandate|
      mandates_to_recalculate &lt;&lt; mandate.id if mandate.activities.count == 1
    end
  end

  def rescore_objects
    contacts_to_recalculate.each do |id|
      contact = Contact.find(id)
      contact.calculate_score
      contact.save!
    end
    contacts_to_recalculate.each do |id|
      mandate = Contact.find(id)
      mandate.calculate_score
      mandate.save!
    end
  end