complete spec and alternative implem

This commit is contained in:
simon lehericey 2022-05-23 11:23:14 +02:00 committed by Paul Chavard
parent abc3681053
commit db5d291acd
2 changed files with 148 additions and 97 deletions

View file

@ -50,137 +50,102 @@ module DossierRebaseConcern
end
def rebase
attachments_to_purge = []
geo_areas_to_delete = []
champs_to_delete = []
# revision we are rebasing to
target_revision = procedure.published_revision
# group changes by stable_id
# { 12 : [ move, update_libelle, update_mandatory ]
changes_by_stable_id = pending_changes
.filter { |change| change[:model] == :type_de_champ }
.group_by { |change| change[:stable_id] }
# index current revision types de champ by stable_id
current_types_de_champ_by_stable_id = revision.types_de_champ.index_by(&:stable_id)
# index published types de champ coordinates by stable_id
target_coordinates_by_stable_id = target_revision
.revision_types_de_champ
.includes(:type_de_champ, :parent)
.index_by(&:stable_id)
# add and remove champs
changes_by_stable_id.each do |stable_id, changes|
type_de_champ = current_types_de_champ_by_stable_id[stable_id]
published_coordinate = target_coordinates_by_stable_id[stable_id]
changes_by_op = pending_changes
.filter { |change| change[:model] == :type_de_champ }
.group_by { |change| change[:op] }
.tap { |h| h.default = [] }
changes.each do |change|
case change[:op]
when :add
add_new_champs_for_revision(published_coordinate)
when :remove
delete_champs_for_revision(type_de_champ)
end
end
end
# add champ
changes_by_op[:add]
.map { |change| change[:stable_id] }
.map { |stable_id| target_coordinates_by_stable_id[stable_id] }
.each { |coordinate| add_new_champs_for_revision(coordinate) }
# find all champs with respective update changes and the published type de champ
champs_with_changes = Champ.where(dossier: self).includes(:type_de_champ).filter_map do |champ|
# type de champ from published revision
type_de_champ = target_coordinates_by_stable_id[champ.stable_id]&.type_de_champ
# only update op changes
changes = (changes_by_stable_id[champ.stable_id] || []).filter { |change| change[:op] == :update }
# remove champ
changes_by_op[:remove]
.each { |change| delete_champs_for_revision(change[:stable_id]) }
if type_de_champ
[champ, type_de_champ, changes]
end
end
changes_by_op[:update]
.map { |change| [change, Champ.joins(:type_de_champ).where(dossier: self, type_de_champ: { stable_id: change[:stable_id] })] }
.each { |change, champs| apply(change, champs) }
# apply changes to existing champs and reset values when needed
update_champs_for_revision(champs_with_changes) do |champ, change, update|
case change[:attribute]
when :type_champ
update[:type] = "Champs::#{change[:to].classify}Champ"
update[:value] = nil
update[:external_id] = nil
update[:data] = nil
geo_areas_to_delete += champ.geo_areas
champs_to_delete += champ.champs
if champ.piece_justificative_file.attached?
attachments_to_purge << champ.piece_justificative_file
end
when :drop_down_options
update[:value] = nil
when :carte_layers
# if we are removing cadastres layer, we need to remove cadastre geo areas
if change[:from].include?(:cadastres) && !change[:to].include?(:cadastres)
geo_areas_to_delete += champ.cadastres
end
end
update[:rebased_at] = Time.zone.now
end
# due to repetition tdc clone on update or erase
# we must reassign tdc to the latest version
Champ
.includes(:type_de_champ)
.where(dossier: self)
.map { |c| [c, target_coordinates_by_stable_id[c.stable_id].type_de_champ] }
.each { |c, target_tdc| c.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) }
# update dossier revision
self.update_column(:revision_id, target_revision.id)
# clear orphaned data
attachments_to_purge.each(&:purge_later)
geo_areas_to_delete.each(&:destroy)
champs_to_delete.each(&:destroy)
end
def add_new_champs_for_revision(published_coordinate)
if published_coordinate.child?
def apply(change, champs)
case change[:attribute]
when :type_champ
champs.each { |champ| champ.piece_justificative_file.purge_later }
GeoArea.where(champ: champs).destroy_all
{
type: "Champs::#{change[:to].classify}Champ",
value: nil,
external_id: nil,
data: nil
}
when :drop_down_options
{ value: nil }
when :carte_layers
# if we are removing cadastres layer, we need to remove cadastre geo areas
if change[:from].include?(:cadastres) && !change[:to].include?(:cadastres)
champs.each { |champ| champ.cadastres.each(&:destroy) }
end
nil
end
&.then { |update_params| champs.update_all(update_params) }
end
def add_new_champs_for_revision(target_coordinate)
if target_coordinate.child?
# If this type de champ is a child, we create a new champ for each row of the parent
parent_stable_id = published_coordinate.parent.stable_id
parent_stable_id = target_coordinate.parent.stable_id
champs_repetition = Champ
.includes(:champs, :type_de_champ)
.where(dossier: self, type_de_champ: { stable_id: parent_stable_id })
champs_repetition.each do |champ_repetition|
champ_repetition.champs.map(&:row).uniq.each do |row|
create_champ(published_coordinate, champ_repetition, row: row)
create_champ(target_coordinate, champ_repetition, row: row)
end
end
else
create_champ(published_coordinate, self)
create_champ(target_coordinate, self)
end
end
def create_champ(published_coordinate, parent, row: nil)
params = { revision: published_coordinate.revision }
def create_champ(target_coordinate, parent, row: nil)
params = { revision: target_coordinate.revision }
params[:row] = row if row
champ = published_coordinate
champ = target_coordinate
.type_de_champ
.build_champ(params)
parent.champs << champ
end
def update_champs_for_revision(champs_with_changes)
champs_with_changes.each do |champ, type_de_champ, changes|
update = {}
changes.each do |change|
yield champ, change, update
end
# update type de champ to reflect new revision
if champ.type_de_champ != type_de_champ
update[:type_de_champ_id] = type_de_champ.id
end
if update.present?
champ.update_columns(update)
end
end
end
def delete_champs_for_revision(type_de_champ)
def delete_champs_for_revision(stable_id)
Champ
.where(dossier: self, type_de_champ: type_de_champ)
.joins(:type_de_champ)
.where(dossier: self, types_de_champ: { stable_id: stable_id })
.destroy_all
end
end

View file

@ -233,6 +233,50 @@ describe Dossier do
dossier.reload
end
context 'with a procedure with a dropdown tdc' do
let!(:procedure) do
create(:procedure).tap do |p|
p.draft_revision.add_type_de_champ(type_champ: :drop_down_list, libelle: 'l1', drop_down_list_value: 'option')
p.publish!
end
end
let!(:dossier) { create(:dossier, procedure: procedure) }
context 'when a dropdown option is changed' do
before do
dossier.champs.first.update(value: 'v1')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
tdc_to_update.update(drop_down_list_value: 'option updated')
end
it { expect { subject }.to change { dossier.champs.first.value }.from('v1').to(nil) }
end
end
context 'with a procedure with a carte tdc' do
let!(:procedure) do
create(:procedure).tap do |p|
champ = p.draft_revision.add_type_de_champ(type_champ: :carte, libelle: 'l1', cadastres: true)
p.publish!
end
end
let!(:dossier) { create(:dossier, procedure: procedure) }
context 'and the cadastre are removed' do
before do
dossier.champs.first.update(value: 'v1', geo_areas: [create(:geo_area, :cadastre)])
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
tdc_to_update.update(cadastres: false)
end
it { expect { subject }.to change { dossier.champs.first.cadastres.count }.from(1).to(0) }
end
end
context 'with a procedure with 2 tdc' do
let!(:procedure) do
create(:procedure).tap do |p|
@ -256,16 +300,27 @@ describe Dossier do
context 'when the first tdc is removed' do
before do
tdc_to_remove = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_remove = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
procedure.draft_revision.remove_type_de_champ(tdc_to_remove.stable_id)
end
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2']) }
end
context 'when the second tdc is moved at the first place' do
before do
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l2')
procedure.draft_revision.move_type_de_champ(stable_id, 0)
end
it { expect { subject }.to change { champ_libelles }.from(['l1', 'l2']).to(['l2', 'l1']) }
end
context 'when the first tdc libelle is updated' do
before do
tdc_to_update = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
tdc_to_update.update(libelle: 'l1 updated')
end
@ -273,19 +328,39 @@ describe Dossier do
end
context 'when the first tdc type is updated' do
def first_champ = dossier.champs.first
before do
tdc_to_update = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
first_champ.update(value: 'v1', external_id: '123', geo_areas: [create(:geo_area)])
first_champ.update(data: { a: 1 })
first_champ.piece_justificative_file.attach(
io: StringIO.new("toto"),
filename: "toto.txt",
content_type: "text/plain",
# we don't want to run virus scanner on this file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'l1')
tdc_to_update = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
tdc_to_update.update(type_champ: :integer_number)
end
it { expect { subject }.to change { dossier.champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
it { expect { subject }.to change { first_champ.class }.from(Champs::TextChamp).to(Champs::IntegerNumberChamp) }
it { expect { subject }.to change { first_champ.value }.from('v1').to(nil) }
it { expect { subject }.to change { first_champ.external_id }.from('123').to(nil) }
it { expect { subject }.to change { first_champ.data }.from({ 'a' => 1 }).to(nil) }
it { expect { subject }.to change { first_champ.geo_areas.count }.from(1).to(0) }
it { expect { subject }.to change { first_champ.piece_justificative_file.attached? }.from(true).to(false) }
end
end
context 'with a procedure with a repetition' do
let!(:procedure) do
create(:procedure).tap do |p|
repetition = p.draft_revision.add_type_de_champ(type_champ: :repetition, libelle: 'r1')
repetition = p.draft_revision.add_type_de_champ(type_champ: :repetition, libelle: 'p1')
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c1', parent_id: repetition.stable_id)
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'c2', parent_id: repetition.stable_id)
p.publish!
@ -333,6 +408,17 @@ describe Dossier do
it { expect { subject }.to change { dossier.champs[0].champs.map(&:type_champ) }.from(['text', 'text']).to(['integer_number', 'text']) }
end
context 'when the parents type is changed' do
before do
stable_id = procedure.draft_revision.types_de_champ.find_by(libelle: 'p1')
parent = procedure.draft_revision.find_or_clone_type_de_champ(stable_id)
parent.update(type_champ: :integer_number)
end
it { expect { subject }.to change { dossier.champs[0].champs.count }.from(2).to(0) }
it { expect { subject }.to change { Champ.count }.from(3).to(1) }
end
end
end
end