Operations

Operations are Lato's system for visually managing the execution of background Jobs.
Operations are handled by several components:

  • The Lato::Operation model initializes all operations and tracks their execution state.
  • The Lato::OperationsController controller manages user access to an ongoing operation.
  • The lato_operation component allows displaying the operation status in the front-end.

Converting a Job into an Operation

The easiest way to convert a Job into an operation is to simply make the Job a subclass of Lato::ApplicationJob.
Additionally, the perform() method must accept a single Hash parameter called params.

# Before
def MyJob < ApplicationJob
  def perform(hello)
    puts hello
  end
end

# After
def MyJob < Lato::ApplicationJob
  def perform(params = {})
    puts params['hello']
  end
end

Generating an Operation

Operations can be generated using the generate method of the Lato::Operation model.
Creating an operation requires three parameters: the name of the job to execute, a Hash of parameters to pass, and the ID of the user requesting the operation.

operation = Lato::Operation.generate('ExportProductsJob', { product_ids: [1, 2, 3] }, @session.user_id)

Once generated, the operation must be started using the start method.
Here is an example controller that starts an operation:

class CustomController < ApplicationController
  def create_operation_action
    @operation = Lato::Operation.generate('OperationExampleJob', {}, @session.user_id)

    respond_to do |format|
      if @operation.start
        format.html { redirect_to lato.operation_path(@operation) }
        format.json { render json: @operation }
      else
        format.html { render :index, status: :unprocessable_entity }
        format.json { render json: @operation.errors, status: :unprocessable_entity }
      end
    end
  end
end

Displaying the Outcome of an Operation

Each operation can be viewed at the route lato.operation_path(operation_id).
To ensure data security, an operation is only accessible to the user who initiated it.

Each operation can have four states: created, running, completed, failed.
The operation view, handled via the lato_operation component, shows the real-time status and output of the operation.
Use the link below to try out an operation:

<%= link_to 'Generate Operation',
  main_app.custom_create_operation_action_path,
  data: { turbo_method: :post }
%>
Generate Operation

Notes

To display the result of an operation directly in-page, you can use lato_action_controller features.
Example:

<%= link_to 'Generate Operation',
  main_app.custom_create_operation_action_path,
  data: { lato_action_target: 'trigger', turbo_method: :post, turbo_frame: 'lato_operation' }
%>
Generate Operation with Success

Handling Operation Outputs

An operation's output can be handled directly within the Job using a series of helper methods.
These functions can also be called even if the Job is executed without using the Lato::Operation model.

Set an output message

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    save_operation_output_message("Operation succeeded with this message")
  end
end

Set an output file

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    file_path = Rails.root.join('tmp', 'myfile.csv')
    save_operation_output_file(file_path)
  end
end

Set an output action

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    save_operation_output_message("Custom operation success message and action")
    save_operation_output_action('Back to homepage', Rails.application.routes.url_helpers.root_path, target: '_blank')
  end
end

Show execution error

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    raise "This is the operation error message"
  end
end

Update progress percentage

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    10.times do |index|
      sleep(1)
      update_operation_percentage((index + 1) * 10)
    end
  end
end

Add logs during operation execution

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    10.times do |index|
      sleep(1)
      add_operation_log("Custom log message #{index + 1}")
    end
  end
end

Replace logs during operation execution

Generate Operation

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    10.times do |index|
      sleep(1)
      replace_operation_log("Custom log message #{index + 1}")
    end
  end
end

Set an input file

<%= form_with
  url: create_operation_action_path(type: 'file_input'),
  data: {
    lato_action_target: 'triggerSubmit',
    turbo_frame: 'lato_operation',
    controller: 'lato-form'
  } do |form| %>

...
class CustomController < ApplicationController
  def create_operation_action
    @operation = Lato::Operation.generate('OperationExampleJob', {}, @session.user_id, params[:file])
    # ...
  end
end
class MyJob < Lato::ApplicationJob
  def perform(params = {})
    save_operation_output_message("You uploaded the file #{operation_input_file_attachment.filename}")
  end
end

Utility Methods

Check if running inside an operation

The operation? method returns true if the job is running inside an operation context, false otherwise.
This is useful for writing jobs that can run both with and without operations:

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    save_operation_output_message("Running inside operation") if operation?
    # ... job logic works even without an operation
  end
end

Clear operation logs

class MyJob < Lato::ApplicationJob
  def perform(params = {})
    add_operation_log("Step 1 started")
    clear_operation_logs # removes all previous logs
    add_operation_log("Restarted from scratch")
  end
end

Operation Query Methods

The Lato::Operation model provides several query methods to inspect an operation's state and output:

operation = Lato::Operation.find(id)

# State checks
operation.finished? # returns true if completed or failed

# Output checks
operation.output_message? # returns true if an output message is set
operation.output_message  # returns the output message string

operation.output_action?  # returns true if an output action is set
operation.output_action   # returns the output action hash

operation.output_error?   # returns true if an output error is set
operation.output_error    # returns the output error string

operation.output_file?    # returns true if an output file is attached
You are offline You are online