models: explicitely save procedure's new revision

Deep-cloned objects have all their relationships stale. Thus, for a
newly deep-cloned revision, `revision.types_de_champs` returns `[]`,
even when it actually has associated types de champ.

This causes consecutive champs creations and re-ordering to fail in
subtle ways, like:

```
procedure.draft_revision.add_type_de_champ(…)
procedure.publish_revision!
procedure.draft_revision.add_type_de_champ(…)
procedure.draft_revision.move_type_de_champ(…) # this will fail
```

As `publish_revision!` created a new stale revision, moving the type
de champ fails because not all existing champs are found until the
object is refreshed.

We don't hit this path in production, because usually only a single
operation is made in a request.

To fix this, save the new revision before associating it as the draft
procedure.

(Another option would be to `reload` the revision after creation, but
this seems better contained and matches the name of the method.)
This commit is contained in:
Pierre de La Morinerie 2021-11-23 13:27:28 +01:00
parent 73faa569e8
commit e5f5440663
2 changed files with 47 additions and 1 deletions

View file

@ -678,7 +678,9 @@ class Procedure < ApplicationRecord
end end
def create_new_revision def create_new_revision
draft_revision.deep_clone(include: [:revision_types_de_champ, :revision_types_de_champ_private]) draft_revision
.deep_clone(include: [:revision_types_de_champ, :revision_types_de_champ_private])
.tap(&:save!)
end end
def average_dossier_weight def average_dossier_weight

View file

@ -695,6 +695,50 @@ describe Procedure do
end end
end end
describe "#publish_revision!" do
let(:procedure) { create(:procedure, :published) }
let(:tdc_attributes) { { type_champ: :number, libelle: 'libelle 1' } }
let(:publication_date) { Time.zone.local(2021, 1, 1, 12, 00, 00) }
before do
procedure.draft_revision.add_type_de_champ(tdc_attributes)
end
subject do
Timecop.freeze(publication_date) do
procedure.publish_revision!
end
end
it 'publishes the new revision' do
subject
expect(procedure.published_revision).to be_present
expect(procedure.published_revision.published_at).to eq(publication_date)
expect(procedure.published_revision.types_de_champ.first.libelle).to eq('libelle 1')
end
it 'creates a new draft revision' do
expect { subject }.to change(ProcedureRevision, :count).by(1)
expect(procedure.draft_revision).to be_present
expect(procedure.draft_revision.revision_types_de_champ).to be_present
expect(procedure.draft_revision.types_de_champ).to be_present
expect(procedure.draft_revision.types_de_champ.first.libelle).to eq('libelle 1')
end
context 'when the procedure has dossiers' do
let(:dossier_draft) { create(:dossier, :brouillon, procedure: procedure) }
let(:dossier_submitted) { create(:dossier, :en_construction, procedure: procedure) }
before { [dossier_draft, dossier_submitted] }
it 'enqueues rebase jobs for draft dossiers' do
subject
expect(DossierRebaseJob).to have_been_enqueued.with(dossier_draft)
expect(DossierRebaseJob).not_to have_been_enqueued.with(dossier_submitted)
end
end
end
describe "#unpublish!" do describe "#unpublish!" do
let(:procedure) { create(:procedure, :published) } let(:procedure) { create(:procedure, :published) }
let(:now) { Time.zone.now.beginning_of_minute } let(:now) { Time.zone.now.beginning_of_minute }