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_save
hook, but that doesn't sound too natural. Is there any other way to pipeline the execution of my jobs?
Many thanks in advance