NickEckhart
NickEckhart

Reputation: 478

How does a rails asynchronous job let the controller know it's done?

I'm using the newest version of rails and ruby. Through a form I import a CSV which then triggers an async job (I'm using the gem https://github.com/brandonhilkert/sucker_punch for this) that reads the file and handles the data.

All in all everything works fine, however:

My question is, how would I let the controller know when the background job is done? I'd like to redirect with a flash notice but obviously I shouldn't do that from the model... What can I do here?

This is my sucker_punch job:

#app/jobs/import_job.rb

class ImportJob
  include SuckerPunch::Job

  def perform(import_file)
    ActiveRecord::Base.connection_pool.with_connection do
      ModelName.import_from_csv(import_file)
    end
  end
end

And this is my controller action:

def import
  ImportJob.new.async.perform(import_file)
  redirect_to list_data_path, notice: 'Data being imported...'
  # i get sent to list_data_path and i stay there even though the background job already finished successfully
end

Upvotes: 2

Views: 3030

Answers (2)

Dausuul
Dausuul

Reputation: 331

Web applications operate strictly on a "pull" model. The client sends a request and the server responds. When you redirect to list_data_path, that is your server response right there. You can't send another response until you get another request.

What this means is, there is no way for the server to reach out to the client and say "Hey, I'm done!" What you have to do instead is put a bit of Javascript on the client side that will ask the server, "You done yet?" That gives the server a chance to reply with "Yes, I'm done" or "No, still working."

Here's a bare-bones example using JQuery:

function processJob(jobId) {
  $.ajax({
    url: "path/to/job/status/" + jobId,
    success: function(result) {
      alert("Job done!");
      window.location("path/to/completed/job/" + jobId);
    },
    error: function(req, statusText, err) {
      if(req.status == 202) 
        setTimeout(function() { processJob(jobId); }, 2000);
      }
      else { alert("Error processing job."); }
  });
}

On the server side, path/to/job/status should be a route that renders status 202 if the job is still processing, 200 if the job is done, and 500 if the job encountered an error. path/to/completed/job should be a route that displays the result of a completed job.

When the user initially submits the file to be processed, call this method on the client side. It will silently monitor the status of the job until it's done, pinging the server every 2 seconds. When the job is done, it will alert the user and pop open the completed job in a new tab.

Upvotes: 1

Cristian Bica
Cristian Bica

Reputation: 4117

For this kind of task I would create a model (ActiveRecord) that holds the status of the job (waiting, processing, done and maybe a progress) and from the controller query the model to present to the use the import job status.

Upvotes: 3

Related Questions