1
\$\begingroup\$

I have a model that is in a HABTM relationship with 2 other models:

Activity.rb

  has_and_belongs_to_many :mandates, -> { distinct }
  has_and_belongs_to_many :contacts, -> { 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
\$\endgroup\$
0
\$\begingroup\$

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 << contact.id if contact.activities.count == 1
    end
    mandates.each do |mandate|
      mandates_to_recalculate << 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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.