6
votes

A user can upload multiple files in my system via a web interface that is handled by a controller action.

  def import
    paths = params[:files].map(&:path) if params[:files].present?
    if paths
      @results = MyRecord.create_from paths
    end 
    redirect_to confirmation_my_records_path
  end

class MyRecord < ActiveRecord::Base
  def self.create_from(paths)
    paths.each do |path|
      MyRecordWorker.perform path 
    end    
  end
end


works/my_record_worker.rb
class MyRecordWorker
  include Sidekiq::Worker
  def perform(path)
      # each time this is run, it is no I/O, just expensive math calculations that take minutes
      collection = ExpensiveOperation.new(path).run 
      if collection && collection.any?
        save_records(collection)
      else
        []
      end
  end
end

Because the sidekiq jobs will run in the background, how do I notify the user through the confirmation page that the jobs are done? We don't know when the job will finish and therefore the Rails request and response cycle cannot determine anything when the confirmation page is loaded. Should the confirmation page just say "Processing..." and then have Faye update the page when the job is finished?

2
This is the perfect time to use web sockets (ActionCable). I've never used Faye, but yes, that should work too.Amin Shah Gilani

2 Answers

6
votes

Add a RecordJobs model to your application that stores who uploaded the records, the number of records, the upload time and so on.

Create a new record_job each time someone uploads new records and pass its id to the worker. The worker can update that instance to indicate its progress.

At the same time, the application can query for existing record_job and show their progress. That can be done whenever the user reloads it page, by active polling or with web sockets (depending on your needs).

3
votes

use ActionCable, here is the example with Report channel

In routes.rb add the following line

mount ActionCable.server => '/cable'

create a ReportChannel.rb inside app/channels with following code

class ReportChannel < ApplicationCable::Channel
  def subscribed 
    stream_from "report_#{current_user.id}"
  end
end

create reports.coffee inside app/assets/javascripts/channels with the following code

  window.App.Channels ||= {}
  window.App.Channels.Report ||= {}
  window.App.Channels.Report.subscribe = ->
      App.report = App.cable.subscriptions.create "ReportChannel",
        connected: ->
          console.log('connected ')

        disconnected: ->
          console.log('diconnected ')

        received: (data) ->
          console.log('returned ')
          data = $.parseJSON(data)
          #write your code here to update or notify user#

and add the following lines at the end of your job

ActionCable.server.broadcast("*report_*#{current_user_id}", data_in_json_format) 

replace report with your specific model

seems simple right :)