如何在 Rails 5.2.4 中从 _Sidekiq Worker 调用控制器操作?
How to call controller action from _Sidekiq Worker in Rails 5.2.4?
我的 RoR 应用程序从 Linux 操作系统触发数据处理脚本(SAS 技术)。 Scheduler::ProductionExecutionsController 中的 execute 方法驱动与脚本的交互。
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
此方法使用以下语法调用:link_to "Go!", execute_scheduler_production_execution_path(execution)
。路线已定义并且按预期工作。
由于某些脚本可能需要一分钟以上的时间才能执行,因此我需要在专门的作业中执行脚本,有时还要安排它们。所以我安装了 Sidekiq 并定义了一个 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
执行排队 Scheduler::ScriptWorker.perform_async(@execution.id)
Sidekiq 运行正常,但每次调用 worker 时,都会引发以下错误:
WARN: NoMethodError: undefined method `execute_scheduler_production_execution_path' for #<Scheduler::ScriptWorker:0x000000000b15ded8>
这样做是否正确,我该如何解决这个问题?
感谢您的帮助!
简短的回答是:你不能
您无法从异步 Sidekiq 工作程序执行重定向。当用户单击“开始!”时,将向您的 Rails 网络应用程序发出 HTTP 请求。 Rails 将请求传递给控制器操作,在您的情况下会产生一个异步 sidekiq 作业。控制器操作逻辑继续并且 Rails 使用 HTTP 响应完成 HTTP 请求,即使您的 sidekiq worker 仍然是 运行。您不能将消息从 sidekiq worker 广播回发起任务的用户
很抱歉这不能解决您的问题。您需要采取不同的方法。
正如 Sean Huber 所说,将前端作业转换为后端作业需要一些架构。通过重构,实验性的 execute 方法被移动到 helper 并重命名为 execute_ssh。控制器中的新 run_once 方法会触发助手的 execute_ssh 方法并重新加载页面。
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
这样,controller 仍然很瘦,worker 可以很容易地重用,逻辑在 helper 模块中。
我的 RoR 应用程序从 Linux 操作系统触发数据处理脚本(SAS 技术)。 Scheduler::ProductionExecutionsController 中的 execute 方法驱动与脚本的交互。
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
此方法使用以下语法调用:link_to "Go!", execute_scheduler_production_execution_path(execution)
。路线已定义并且按预期工作。
由于某些脚本可能需要一分钟以上的时间才能执行,因此我需要在专门的作业中执行脚本,有时还要安排它们。所以我安装了 Sidekiq 并定义了一个 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
执行排队 Scheduler::ScriptWorker.perform_async(@execution.id)
Sidekiq 运行正常,但每次调用 worker 时,都会引发以下错误:
WARN: NoMethodError: undefined method `execute_scheduler_production_execution_path' for #<Scheduler::ScriptWorker:0x000000000b15ded8>
这样做是否正确,我该如何解决这个问题? 感谢您的帮助!
简短的回答是:你不能
您无法从异步 Sidekiq 工作程序执行重定向。当用户单击“开始!”时,将向您的 Rails 网络应用程序发出 HTTP 请求。 Rails 将请求传递给控制器操作,在您的情况下会产生一个异步 sidekiq 作业。控制器操作逻辑继续并且 Rails 使用 HTTP 响应完成 HTTP 请求,即使您的 sidekiq worker 仍然是 运行。您不能将消息从 sidekiq worker 广播回发起任务的用户
很抱歉这不能解决您的问题。您需要采取不同的方法。
正如 Sean Huber 所说,将前端作业转换为后端作业需要一些架构。通过重构,实验性的 execute 方法被移动到 helper 并重命名为 execute_ssh。控制器中的新 run_once 方法会触发助手的 execute_ssh 方法并重新加载页面。
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
这样,controller 仍然很瘦,worker 可以很容易地重用,逻辑在 helper 模块中。