# 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