I’m working on an Rails app which syncs data with a 3rd party. It turned out though that when one change was made in my application, it unfortunately needed to make 3 different sync requests to the 3rd party application. And, the 3rd party app is finicky. The request need to be in order, one at a time.
I needed to chain these requests together, but really did not want to introduce any of the gems I could find that would help me. The gems were just “too much” for what I needed. I ended up writing a “Command” class to call the various workers. The command object would include itself as a param to the Sidekiq worker and the worker would then call the command class as part of it’s last step. It ended up looking like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'json' | |
class AddIndividiualCmd | |
attr_reader :roster_id, :current_step | |
STEPS = %w( | |
PhoneInsertWorker | |
RosterLinkToSchoolWorker | |
RosterLinkToSchoolAddressWorker | |
) | |
def initialize(roster_id, current_step = nil) | |
@roster_id = roster_id | |
@current_step = current_step || STEPS[0] | |
end | |
def run | |
case @current_step | |
when "PhoneInsertWorker" | |
worker = Object.const_get(@current_step) | |
roster = Roster.find(@roster_id) | |
worker.perform_async(roster.phone.id, self.to_json) | |
when "RosterLinkToSchoolWorker" | |
worker = Object.const_get(@current_step) | |
worker.perform_async(@roster_id, self.to_json) | |
when "RosterLinkToSchoolAddressWorker" | |
worker = Object.const_get(@current_step) | |
worker.perform_async(@roster_id, self.to_json) | |
else | |
Rails.logger.info "Finished AddIndividiualCmd for roster [#{@roster_id}]" | |
end | |
end | |
def next | |
current_step_index = STEPS.index(@current_step) | |
@current_step = current_step_index.nil? ? nil : STEPS[(current_step_index + 1)] | |
run | |
end | |
def to_json(*a) | |
{ | |
"json_class" => self.class.name, | |
"data" => {'roster_id' => @roster_id, 'current_step' => @current_step} | |
}.to_json(*a) | |
end | |
def self.json_create(o) | |
new(o['data']['roster_id'], o['data']['current_step']) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RosterLinkToSchoolWorker | |
include Sidekiq::Worker | |
include MyHelper | |
def perform(roster_id, cmd_json = nil) | |
roster = Roster.find(roster_id) | |
link_roster_to_school(roster, cmd_json) | |
end | |
def link_roster_to_school(roster, cmd_json) | |
my_helper_method(roster) | |
if cmd_json.present? | |
cmd = JSON.load(cmd_json) | |
cmd.next() | |
end | |
end | |
end |
The command class needs to be able to serialize and deserialize from JSON since that is how Sidekiq persists parameters. I wasn’t sure why I needed to do the deserialization in the worker myself, but it “worked”.
One thing I like about this strategy better than having a worker directly call the next worker is that from this command class, I can see exactly what workers are going to be called and in what order. I can also use the workers independently, without being chained. And, if I have another command that needs to call a worker but getting the worker param is different, that logic is encapsulated in the command class. Not spread around who knows where.
I still consider myself relatively new to Ruby and I’m not sure of the merits of Object.const_get vs. some other method, so feedback is appreciated.
Version Info:
- Rails 4.2.7
- Ruby 2.2.4
- Sidekiq 4.1.4
https://gist.github.com/aghuddleston/c3051a563117996a1e659fcef48812d6#file-addindividualcmd-rb