af83/chouette-core

View on GitHub
app/models/macro/list.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

module Macro
  class List < ApplicationModel
    self.table_name = 'macro_lists'

    belongs_to :workbench, optional: false
    validates :name, presence: true

    has_many :macros, lambda {
                        order(position: :asc)
                      }, class_name: 'Macro::Base', dependent: :delete_all, foreign_key: 'macro_list_id', inverse_of: :macro_list
    has_many :macro_list_runs, class_name: 'Macro::List::Run', foreign_key: :original_macro_list_id
    has_many :macro_contexts, class_name: 'Macro::Context', foreign_key: 'macro_list_id', inverse_of: :macro_list

    has_many :processing_rules, class_name: 'ProcessingRule::Base', as: :processable

    accepts_nested_attributes_for :macros, allow_destroy: true, reject_if: :all_blank
    accepts_nested_attributes_for :macro_contexts, allow_destroy: true, reject_if: :all_blank

    scope :by_text, ->(text) { text.blank? ? all : where('lower(name) LIKE :t', t: "%#{text.downcase}%") }

    def used?
      processing_rules.exists?
    end

    # macro_list_run = macro_list.build_run user: user, workbench: workbench, referential: target
    #
    # if macro_list_run.save
    #   macro_list_run.enqueue
    # else
    #   render ...
    # end

    class Run < Operation
      # The Workbench where macros are executed
      self.table_name = 'macro_list_runs'

      belongs_to :workbench, optional: false
      delegate :workgroup, to: :workbench

      # The Referential where macros are executed.
      # Optional, because the user can run macros on Stop Areas for example
      belongs_to :referential, optional: true

      # The original macro list definition. This macro list can have been modified or deleted since.
      # Should only used to provide a link in the UI
      belongs_to :original_macro_list, optional: true, foreign_key: :original_macro_list_id, class_name: 'Macro::List'

      has_many :macro_runs, -> { order(position: :asc) }, class_name: 'Macro::Base::Run',
                                                          dependent: :destroy, foreign_key: 'macro_list_run_id'

      has_many :macro_context_runs, class_name: 'Macro::Context::Run', dependent: :destroy,
                                    foreign_key: 'macro_list_run_id', inverse_of: :macro_list_run

      has_many :macro_messages, class_name: 'Macro::Message', through: :macro_runs
      has_many :context_macro_messages,
               class_name: 'Macro::Message',
               through: :macro_context_runs,
               source: :macro_messages

      has_one :processing, as: :processed

      validates :name, presence: true
      validates :original_macro_list_id, presence: true, if: :new_record?

      scope :having_status, ->(statuses) { where(user_status: statuses) }
      scope :purgeable, -> { where("created_at < ?", 90.days.ago) }

      def purge_older
        workbench.macro_list_runs.purgeable.in_batches.destroy_all if workbench
      end

      after_create :purge_older

      def build_with_original_macro_list
        return unless original_macro_list

        original_macro_list.macros.each do |macro|
          macro_runs << macro.build_run
        end

        original_macro_list.macro_contexts.each do |macro_context|
          macro_context_runs << macro_context.build_run
        end
      end

      def perform
        referential.switch if referential

        macro_runs.each(&:run)
        macro_context_runs.each(&:run)
      end

      def final_user_status
        UserStatusFinalizer.new(self).user_status
      end

      class UserStatusFinalizer
        def initialize(macro_list_run)
          @macro_list_run = macro_list_run
        end
        attr_reader :macro_list_run

        delegate :macro_messages, :context_macro_messages, to: :macro_list_run

        def criticities
          @criticities ||= (without_context_criticities + with_context_criticities).uniq
        end

        def worst_criticity
          %w[error warning].find do |criticity|
            criticity.in?(criticities)
          end
        end

        def user_status
          case worst_criticity
          when 'error'
            Operation.user_status.failed
          when 'warning'
            Operation.user_status.warning
          else
            Operation.user_status.successful
          end
        end

        private

        def without_context_criticities
          # reorder! to avoid problems with default order and pluck
          macro_messages.reorder!.distinct.pluck(:criticity)
        end

        def with_context_criticities
          context_macro_messages.reorder!.distinct.pluck(:criticity)
        end
      end

      def base_scope
        if referential
          Scope::Referential.new(workbench, referential)
        else
          Scope::Workbench.new(workbench)
        end
      end

      def owned_scope
        Scope::Owned.new(base_scope, workbench)
      end

      def scope
        owned_scope
      end
    end
  end
end