My RoR application triggers data treatment scripts (SAS technology) from Linux operating system. An execute method in the Scheduler::ProductionExecutionsController drives the interactions with the scripts.
Scheduler::ProductionExecutionsController
# POST /production_executions/1/execute
def execute
@production_execution = ProductionExecution.find(params[:id])
@production_execution.update_attributes(started_at: Time.now,
status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)
--- Scripts interaction ---
@production_execution.update_attributes(ended_at: Time.now,
status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
source_records_count: global_input_count,
processed_count: global_output_count,
error_message: nil
)
respond_to do |format|
format.html { redirect_back fallback_location: @production_execution, notice: @msg }
format.js
end
This method is called using this syntax: link_to "Go!", execute_scheduler_production_execution_path(execution)
. The route is defined and it works as expected.
As some scripts can take over a minute to execute, I need to execute the scripts in dedicated jobs, and sometimes schedule them. So I installed Sidekiq and defined a Scheduler::ScriptWorker :
class Scheduler::ScriptWorker
include Sidekiq::Worker
sidekiq_options queue: :default, tags: ['script']
def perform(execution_id)
puts "Sidekiq job start"
puts execution_id
redirect_to execute_scheduler_production_execution_path(execution_id) and return
end
end
The executions are queued with Scheduler::ScriptWorker.perform_async(@execution.id)
Sidekiq operates correctly, but each time the worker is invoked, it raise the following error:
WARN: NoMethodError: undefined method `execute_scheduler_production_execution_path' for #<Scheduler::ScriptWorker:0x000000000b15ded8>
Is it the right way to do this, and how can I solve this issue? Thanks for you help!
As stated by Sean Huber, converting a front-end job into a backend job requires a touch of architecture. Through refactoring, the experimental execute method was moved to a helper and renamed execute_ssh. A new run_once method in the controller fires the helper's execute_ssh method and reloads the page.
script_worker.rb
class Scheduler::ScriptWorker
include Sidekiq::Worker
include SchedulerExecutionHelper
include ParametersHelper
sidekiq_options queue: :default, tags: ['script'], retry: false
def perform(execution_id)
puts "Sidekiq job start"
puts execution_id
execute_ssh execution_id
end
end
scheduler_execution_helper.rb
def execute_ssh(execution_id)
puts "--- Switched to helper"
@production_execution = ProductionExecution.find(execution_id)
puts @production_execution.id
@production_execution.update_attributes(started_at: Time.now, status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)
--- Scripts interaction ---
@production_execution.update_attributes(ended_at: Time.now,
status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
source_records_count: global_input_count,
processed_count: global_output_count,
error_message: nil
)
end
production_schedules_controller
def run_once
@job = @production_schedule.parent
@execution = @job.production_executions.build(playground_id: @job.playground_id,
production_job_id: @job.id,
environment_id: @production_schedule.environment_id,
owner_id: current_user.id,
status_id: options_for('Statuses', 'Scheduler').find { |x| x["code"] == "READY" }.id || 0)
if @execution.save
@job.production_events.where(production_execution_id: nil).each do |event|
execution_event = event.dup
execution_event.production_execution_id = @execution.id
execution_event.return_value = 0
execution_event.status_id = statuses.find { |x| x["code"] == "READY" }.id
execution_event.save
end
Scheduler::ScriptWorker.perform_async(@execution.id)
redirect_to scheduler_production_job_path(@job)
else
end
end
This way, the controller remains thin, the worker can be easily reused, and the logic is in the helper module.