Newer
Older
dmpopidor / app / services / template / upgrade_customization_service.rb
# frozen_string_literal: true

class Template

  # Service object to upgrade a customization Template with new changes from the original
  # funder Template. Remember: {target_template} is a customization of funder Template.
  #
  # - Duplicate the init template (Duplication called {#source_template})
  #
  # - Create a new customisation of funder template (Customization called
  #   {#target_template})
  #
  # - Take each phase on the {#target_template} and iterate to find if there's a
  #   corresponding one in {#source_template}
  #   - Test for each of a) no corresponding phase in source,
  #                      b) corresponding found
  #     a) Create a duplicate phase on the source_template
  #     b) Do nothing at this point
  #   - Copy over each of the modifiable sections from source to the target
  #     - Re-number the sections if necessary to keep the display order (number) the same
  #     - Copy each of the questions and annotations exactly
  #   - For each unmodifiable section, copy over any modifiable questions from target
  #
  # - Copy each of the modifiable sections from the {#source_template} to the
  #   {#target_template}
  #
  class UpgradeCustomizationService

    # Exception raised when the Template is not a customization.
    class NotACustomizationError < StandardError
    end

    # Exception raised when no published funder Template can be found.
    class NoFunderTemplateError < StandardError
    end


    ##
    # The Template we're upgrading
    #
    # Returns {Template}
    attr_reader :init_template

    # Initialize a new instance and run the script
    #
    # template - The Template we're upgrading
    #
    # Returns {Template}
    def self.call(template)
      new(template).call
    end

    private_class_method :new

    # Initialize a new record
    #
    # template - The Template we're upgrading. Sets the value for {#init_template}
    #
    def initialize(template)
      @init_template = template
    end

    # Run the script
    #
    # Returns {Template}
    def call
      if init_template.customization_of.blank?
        raise NotACustomizationError,
          _("upgrade_customization! requires a customised template")
      end
      if funder_template.nil?
        # rubocop:disable Metrics/LineLength
        raise NoFunderTemplateError,
          _("upgrade cannot be carried out since there is no published template of its current funder")
        # rubocop:enable Metrics/LineLength
      end

      # Merges modifiable sections or questions from source into target_template object
      target_template.phases.map do |target_phase|
        # Search for the phase in the source template whose versionable_id matches the
        # customization_phase
        #
        # a) If the Org's template ({#source_template}) has the Phase...
        if source_phase = find_matching_record_in_collection(
                            record: target_phase,
                            collection: source_template.phases)

        # b) If the Org's template ({#source_template}) doesn't have this Phase.
        #    This is not a problem, since {#customization_template} should have this Phase
        #    copied over from {#template_phase}.
        else
          next
        end

        copy_modifiable_sections_for_phase(source_phase, target_phase)
        sort_sections_within_phase(target_phase)
      end

      copy_custom_annotations_for_questions

      return target_template
    end

    private

    # The funder Template for this {#template}
    #
    # Returns Template
    def funder_template
      @funder_template ||= Template.published(init_template.customization_of).first
    end

    # A copy of the Template we're currently upgrading. Preserves modifiable flags from
    # the self template copied
    #
    #
    # Returns {Template}
    def source_template
      @source_template ||= init_template.deep_copy(attributes: {
                                                     version: init_template.version + 1,
                                                     published: false
                                                   })
    end

    # Creates a new customisation for the published template whose family_id {#template}
    # is a customization of
    #
    # Returns {Template}
    def target_template
      @target_template ||= funder_template.deep_copy(
        attributes: {
          version: source_template.version,
          published: source_template.published,
          family_id: source_template.family_id,
          customization_of: source_template.customization_of,
          org: source_template.org,
          visibility: Template.visibilities[:organisationally_visible],
          is_default: false
        }, modifiable: false, save: true
      )
    end

    # Find an item within collection that has the same versionable_id as record
    #
    # record      - The record we're searching for a match of
    # collection  - The collection of records we're searching in
    #
    # Returns Positionable
    #
    # Returns nil
    def find_matching_record_in_collection(record:, collection:)
      collection.detect { |item| item.versionable_id == record.versionable_id }
    end

    # Attach modifiable sections into the customization_phase
    #
    # source_phase - A Phase to copy sections for.
    # target_phase - A Phase to copy Sections to.
    #
    # Returns Array of Sections
    def copy_modifiable_sections_for_phase(source_phase, target_phase)
      source_phase.sections.select(&:modifiable?).each do |section|
        target_phase.sections << section
      end
    end

    def copy_custom_annotations_for_questions
      init_template.annotations.where(org: template_org).each do |custom_annotation|
        target_question = target_template.questions.find_by(
          versionable_id: custom_annotation.question.versionable_id
        )
        target_question.annotations << custom_annotation
      end
    end

    def sort_sections_within_phase(phase)
      phase.sections = SectionSorter.new(*phase.sections).sort!
    end

    def template_org
      init_template.org
    end

  end

end