3scale/porta

View on GitHub
app/workers/delete_plain_object_worker.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

class DeletePlainObjectWorker < ApplicationJob
  include Sidekiq::Throttled::Job

  # TODO: Rails 5 --> discard_on ActiveJob::DeserializationError, ActiveRecord::RecordNotFound
  rescue_from(ActiveJob::DeserializationError, ActiveRecord::RecordNotFound) do |exception|
    Rails.logger.info "DeletePlainObjectWorker#perform raised #{exception.class} with message #{exception.message}"
  end

  rescue_from(ActiveRecord::RecordNotDestroyed) do |exception|
    if object.class.exists?(object.id)
      # We don't want to indefinitely try again to delete an object that for any reason can not be destroyed, so we just log it instead
      System::ErrorReporting.report_error(exception, parameters: { caller_worker_hierarchy: caller_worker_hierarchy,
                                                                   error_messages: exception.record.errors.full_messages })
    end
  end

  rescue_from(ActiveRecord::StaleObjectError) do |exception|
    Rails.logger.info "DeletePlainObjectWorker#perform raised #{exception.class} with message #{exception.message} for the hierarchy #{caller_worker_hierarchy}"
    retry_job if object.class.exists?(object.id)
  end

  queue_as :default

  sidekiq_throttle concurrency: { limit: 10 }

  before_perform do |job|
    @object, workers_hierarchy, @destroy_method = job.arguments
    @id = "Plain-#{object.class.name}-#{object.id}"
    @caller_worker_hierarchy = Array(workers_hierarchy) + [@id]
    info "Starting #{job.class}#perform with the hierarchy of workers: #{caller_worker_hierarchy}"
  end

  after_perform do |job|
    info "Finished #{job.class}#perform with the hierarchy of workers: #{caller_worker_hierarchy}"
  end

  def perform(_object, _caller_worker_hierarchy = [], _destroy_method = 'destroy')
    should_destroy_by_association? ? destroy_by_association : object.public_send(destroy_method(bang_if_possible: true))
  end

  private

  delegate :info, to: 'Rails.logger'

  attr_reader :caller_worker_hierarchy, :id, :object

  def destroy_method(bang_if_possible: false)
    object_destroy_method = @destroy_method.presence || 'destroy'

    (bang_if_possible && object_destroy_method == 'destroy') ? 'destroy!' : object_destroy_method
  end

  def should_destroy_by_association?
    # If there is 1 only it means caller_worker_hierarchy argument to perform was an empty array and that 1 element is this DeletePlainObjectWorker instance itself
    # If there are 2 it means that there is a DeleteObjectHierarchyWorker object (position 0 of the Array) that called to this DeletePlainObjectWorker instance itself (position 1 of the Array)
    # More than 2 it means there is a chain of DeleteObjectHierarchyWorker objects before reaching this DeletePlainObjectWorker and that chain is the 'delete by association'.
    # For example this could be a Service object with ID 3 of a Provider object with ID 2 and the chain at this point would look like [Hierarchy-Provider-2, Hierarchy-Service-3, Plain-Service-3].
    # This 'code' is for logging purposes only and Hierarchy means DeleteObjectHierarchyWorker and Plain means DeletePlainObjectWorker, then the model and the ID.
    # You can see more about this argument and what should happen here in DeletePlainObjectWorkerTest
    caller_worker_hierarchy.length > 2
  end

  def destroy_by_association
    object.destroyed_by_association = DummyDestroyedByAssociationReflection.new(id)
    object.public_send(destroy_method)
  end

  class DummyDestroyedByAssociationReflection
    def initialize(foreign_key)
      @foreign_key = foreign_key
    end
    attr_reader :foreign_key
  end
  private_constant :DummyDestroyedByAssociationReflection

end