af83/chouette-core

View on GitHub
app/models/control/code_uniqueness.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Control
  class CodeUniqueness < Control::Base
    module Options
      extend ActiveSupport::Concern

      included do
        option :target_model
        option :target_code_space_id
        option :uniqueness_scope

        enumerize :target_model, in: %w[StopArea Company Line Document Entrance PointOfInterest Shape]
        enumerize :uniqueness_scope, in: %w[workgroup workbench provider]

        validates :target_model, :target_code_space_id, presence: true

        def target_code_space
          @target_code_space ||= code_space_scope.find_by(id: target_code_space_id)
        end

        def code_space_scope
          return workgroup.code_spaces unless uniqueness_scope == 'workgroup'

          CodeSpace
        end
      end
    end

    include Options

    class Run < Control::Base::Run
      include Options

      def run
        analysis.duplicates.each do |duplicate|
          create_message(duplicate)
        end
      end

      def create_message(duplicate)
        control_messages.create!(
          message_attributes: {
            code_space: target_code_space.short_name,
            name: duplicate.name, code_value: duplicate.code_value
          },
          criticity: criticity, message_key: :code_uniqueness,
          source_id: duplicate.source_id, source_type: duplicate.source_type
        )
      end

      def analysis
        Analysis.for(uniqueness_scope).new(context, target_model, target_code_space)
      end

      class Analysis
        def self.for(uniqueness_scope)
          const_get(uniqueness_scope.classify)
        end

        class Base
          def initialize(context, target_model, target_code_space)
            @context = context
            @target_model = target_model
            @target_code_space = target_code_space
          end

          attr_accessor :target_model, :context, :target_code_space

          PREFIX_PROVIDERS = {
            stop_area: 'stop_area',
            company: 'line',
            line: 'line',
            document: 'document',
            entrance: 'stop_area',
            point_of_interest: 'shape',
            shape: 'shape'
          }.with_indifferent_access

          def duplicates
            PostgreSQLCursor::Cursor.new(query).map do |attributes|
              Duplicate.new attributes.merge source_type: source_type
            end
          end

          def model_singular
            @model_singular ||= target_model.underscore
          end

          def source_type
            @source_type ||= if target_model == 'PointOfInterest'
                               'PointOfInterest::Base'
                             else
                               begin
                                 "Chouette::#{target_model}".constantize
                               rescue StandardError
                                 target_model.constantize
                               end.to_s
                             end
          end

          def model_collection
            @model_collection ||= model_singular.pluralize
          end

          def model_table_name
            @model_table_name ||= models.table_name
          end

          def models
            context.send(model_collection)
          end

          def provider_id
            "#{model_collection}.#{PREFIX_PROVIDERS[model_singular]}_provider_id"
          end

          def providers
            "#{PREFIX_PROVIDERS[model_singular]}_providers"
          end

          def where
            <<~SQL
              WHERE (codes.resource_type = '#{source_type}')
                AND (codes.code_space_id = #{target_code_space.id})
            SQL
          end

          def query
            <<~SQL
              SELECT * FROM (
                SELECT #{model_table_name}.*, codes.value AS code_value, #{duplicates_count} AS duplicates_count
                FROM #{model_table_name}
                #{inner_join}
                #{where}
              ) AS with_duplicates_count
              WHERE duplicates_count > 1 AND (id IN (#{models.select(:id).to_sql}))
            SQL
          end

          class Duplicate
            def initialize(attributes)
              attributes.each { |k, v| send "#{k}=", v if respond_to?(k) }
            end

            attr_accessor :name, :source_type, :code_value, :id

            alias source_id id
          end
        end

        class Provider < Base
          def duplicates_count
            <<~SQL
              count(#{model_table_name}.id) OVER(PARTITION BY #{provider_id}, codes.value)
            SQL
          end

          def inner_join
            <<~SQL
              INNER JOIN public.codes ON codes.resource_id = #{model_table_name}.id
            SQL
          end
        end

        class Workbench < Base
          def duplicates_count
            <<~SQL
              count(#{model_table_name}.id) OVER(PARTITION BY workbenches.id, codes.value)
            SQL
          end

          def inner_join
            <<~SQL
              INNER JOIN public.#{providers} ON #{providers}.id = #{provider_id}
              INNER JOIN public.workbenches ON workbenches.id = #{providers}.workbench_id
              INNER JOIN public.codes ON codes.resource_id = #{model_table_name}.id
            SQL
          end
        end

        class Workgroup < Base
          def duplicates_count
            <<~SQL
              count(#{model_table_name}.id) OVER(PARTITION BY workgroups.id, codes.value)
            SQL
          end

          def inner_join
            <<~SQL
              INNER JOIN public.#{providers} ON #{providers}.id = #{provider_id}
              INNER JOIN public.workbenches ON workbenches.id = #{providers}.workbench_id
              INNER JOIN public.workgroups ON workgroups.id = workbenches.workgroup_id
              INNER JOIN public.codes ON codes.resource_id = #{model_table_name}.id
            SQL
          end

          def where
            <<~SQL
              WHERE (codes.resource_type = '#{source_type}')
                AND (codes.code_space_id = #{target_code_space.id})
                AND (workgroups.id = #{context.workgroup.id})
            SQL
          end
        end
      end
    end
  end
end