Run Rails jobs sequentially

2019-08-22 01:21发布

I have three Rails jobs to process a player yellow/red cards in a soccer tournament, and the penalties these players will have due to getting this cards.
The idea is that the first job collects all Incidences (an Incidence is when a Player gets a yellow card, to give an example), and counts all the cards a Player got.

class ProcessBookedPlayersJob < ApplicationJob
  queue_as :default
  @cards = []

  def perform(*args)
    @cards = []
    yellows = calculate_cards(1)
    reds = calculate_cards(2)
    @cards << yellows << reds
  end

  def after_perform(*match)
    #ProcessPenaltiesJob.perform_later @cards
    ProcessPenalties.perform_later @cards
    #PenaltiesFinalizerJob.perform_later match
    PenaltiesFinalizer.perform_later match
  end

  def calculate_cards(card_type)
    cards = Hash.new
    players = Player.fetch_players_with_active_incidences
    players.each { |p|
      # 1 is yellow, 2 is red
      counted_cards = Incidence.incidences_for_player_id_and_incidence_type(p.id, card_type).size
      cards[p] = counted_cards
    }
    return cards
  end
end

This first job is executed when an Instance is created.

class Incidence < ApplicationRecord
  belongs_to :player
  belongs_to :match

  after_save :process_incidences, on: :create

  def self.incidences_for_player_id_and_incidence_type(player_id, card_type)
    return Incidence.where(status: 1).where(incidence_type: card_type).where(player_id: player_id)
  end

  protected
  def process_incidences
    ProcessBookedPlayers.perform_later
  end
end

After this, another job runs and creates the necessary Penalties (a Penalty is a ban for the next Match, for example) according to the Hash output that the previous job created.

class ProcessPenaltiesJob < ApplicationJob
  queue_as :default

  def perform(*cards)
    yellows = cards[0]
    reds = cards[1]
    create_penalties_for_yellow_cards(yellows)
    create_penalties_for_red_cards(reds)
  end

  # rest of the job...

And also there's another job, that sets these bans as disabled, once they have expired.

class PenaltiesFinalizerJob < ApplicationJob
  queue_as :default

  def perform(match)
    active_penalties = Penalty.find_by(status: 1)
    active_penalties.each do |p|
      #penalty.starting_match.order + penalty.length == el_match_que_inserte.order (ver si seria >=)
      if p.match.order + p.length >= match.order
        p.status = 2    # Inactivate
        p.save!
      end
    end
  end
end

As you can see in ProcessBookedPlayersJob's after_perform method

def after_perform(*match)
    ProcessPenalties.perform_later @cards
    PenaltiesFinalizer.perform_later match
end

I'm trying to get those two other jobs executed (ProcessPenaltiesJob and PenaltiesFinalizerJob) with no luck. The job ProcessBookedPlayersJob is being executed (because I can see this in the log)

[ActiveJob] [ProcessBookedPlayersJob] [dbb8445e-a706-4443-9cb8-2c45f49a4f8f] Performed ProcessBookedPlayersJob (Job ID: dbb8445e-a706-4443-9cb8-2c45f49a4f8f) from Async(default) in 38.81ms

But the other two jobs aren't executed. So, how can I get both ProcessPenaltiesJob and PenaltiesFinalizerJob run after ProcessBookedPlayersJob has finalized its execution? I don't mind if they run in parallel, but they need to be run after the first one finishes, since they need its output as their input.

I have searched for this, and the closest match I found was this answer. Quoting it:

If the sequential jobs you are talking about however are of different jobs / class, then you can just call the other job once the first job has finished.

That's exactly the behaviour I'm trying to have... but how can I get my jobs to run sequentially?
For now, I'm thinking in adding the first job's logic into Incidences's after_savehook, but that doesn't sound too natural. Is there any other way to pipeline the execution of my jobs?
Many thanks in advance

0条回答
登录 后发表回答