From a3c17a23cc9b09757ebffee31fb0ae7aa9ea83b5 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 3 Jul 2026 09:24:59 +0100 Subject: [PATCH 01/17] Allow exit page access for multiple branches --- app/controllers/pages/exit_page_controller.rb | 8 ++++++-- spec/requests/pages/exit_page_controller_spec.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/controllers/pages/exit_page_controller.rb b/app/controllers/pages/exit_page_controller.rb index 9ba623ada1..2f9c74d823 100644 --- a/app/controllers/pages/exit_page_controller.rb +++ b/app/controllers/pages/exit_page_controller.rb @@ -1,6 +1,6 @@ class Pages::ExitPageController < PagesController before_action :can_add_page_routing, only: %i[new create delete destroy] - before_action :ensure_answer_value_present, only: %i[new create] + before_action :ensure_answer_value_present, only: %i[new create], unless: -> { current_form&.group.present? && FeatureService.new(group: current_form.group).enabled?(:multiple_branches) } def new exit_page_input = Pages::ExitPageInput.new(form: current_form, page:, answer_value: params[:answer_value]) @@ -81,7 +81,11 @@ def render_preview private def can_add_page_routing - authorize current_form, :can_add_page_routing_conditions? + if current_form&.group.present? && FeatureService.new(group: current_form.group).enabled?(:multiple_branches) + authorize current_form, :can_edit_form? + else + authorize current_form, :can_add_page_routing_conditions? + end end def exit_page_input_params diff --git a/spec/requests/pages/exit_page_controller_spec.rb b/spec/requests/pages/exit_page_controller_spec.rb index 92459c6e69..01b325a9cc 100644 --- a/spec/requests/pages/exit_page_controller_spec.rb +++ b/spec/requests/pages/exit_page_controller_spec.rb @@ -44,6 +44,12 @@ expect(response.status).to eq(403) expect(response).to render_template("errors/forbidden") end + + context "when mutlple branches is enabled", :multiple_branches do + it "renders the new exit page template" do + expect(response).to render_template("pages/exit_page/new") + end + end end context "when there is no answer value param" do @@ -93,6 +99,13 @@ expect(response.status).to eq(403) expect(response).to render_template("errors/forbidden") end + + context "when mutlple branches is enabled", :multiple_branches do + it "Renders the forbidden page" do + expect(response.status).to eq(403) + expect(response).to render_template("errors/forbidden") + end + end end end From ca321075d9cab81abd8e6f3837e35b5b2da2fe94 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Mon, 6 Jul 2026 08:57:28 +0100 Subject: [PATCH 02/17] fixup! Allow exit page access for multiple branches --- spec/requests/pages/exit_page_controller_spec.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/requests/pages/exit_page_controller_spec.rb b/spec/requests/pages/exit_page_controller_spec.rb index 01b325a9cc..9e943c3e13 100644 --- a/spec/requests/pages/exit_page_controller_spec.rb +++ b/spec/requests/pages/exit_page_controller_spec.rb @@ -44,12 +44,6 @@ expect(response.status).to eq(403) expect(response).to render_template("errors/forbidden") end - - context "when mutlple branches is enabled", :multiple_branches do - it "renders the new exit page template" do - expect(response).to render_template("pages/exit_page/new") - end - end end context "when there is no answer value param" do From 43dba18e5c7fec349bda0e05ab7de10e38732a0d Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Mon, 6 Jul 2026 08:57:48 +0100 Subject: [PATCH 03/17] WIP: Read/Write from ExitPage model --- app/input_objects/pages/conditions_input.rb | 3 + app/models/condition.rb | 63 ++++++++++++++++++++- spec/models/condition_spec.rb | 24 ++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/app/input_objects/pages/conditions_input.rb b/app/input_objects/pages/conditions_input.rb index 0c6de0b566..a72ff5ced4 100644 --- a/app/input_objects/pages/conditions_input.rb +++ b/app/input_objects/pages/conditions_input.rb @@ -32,6 +32,9 @@ def update_condition record.skip_to_end = skip_to_end record.goto_page_id = goto_page_id + exit_page_to_delete = record&.exit_page + record.exit_page_id = nil + exit_page_to_delete.destroy! if exit_page_to_delete record.exit_page_heading = nil record.exit_page_markdown = nil end diff --git a/app/models/condition.rb b/app/models/condition.rb index 30a1250a2b..dfb0540ba4 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -16,19 +16,44 @@ class Condition < ApplicationRecord translates :exit_page_heading translates :exit_page_markdown, presence: false # Without presence here, the value is nil if set to "" - def self.create_and_update_form!(...) - condition = Condition.new(...) + def self.create_and_update_form!(**args) + exit_page_attributes = args.slice(:exit_page_heading, :exit_page_markdown) + + condition = Condition.new(**args) + + if exit_page_attributes.present? + heading = exit_page_attributes.fetch(:exit_page_heading, "") + markdown = exit_page_attributes.fetch(:exit_page_markdown, "") + condition.exit_page = ExitPage.create(heading:, markdown:, question_page: condition.routing_page) + end + condition.save_and_update_form condition end - def save_and_update_form + def save_and_update_form(**args) + exit_page_attributes = args.slice(:exit_page_heading, :exit_page_markdown) + + if exit_page_attributes.present? + heading = exit_page_attributes.fetch(:exit_page_heading, "") + markdown = exit_page_attributes.fetch(:exit_page_markdown, "") + + if exit_page.present? + exit_page.update!(heading:, markdown:) + else + ExitPage.create!(heading:, markdown:, question_page: routing_page) + end + end + save! form.save_question_changes! end def destroy_and_update_form! + exit_page_to_delete = exit_page destroy! && form.save_question_changes! + + exit_page_to_delete.presence&.destroy! unless FeatureService.new(group: form.group).enabled?(:multiple_branches) end def validation_errors @@ -40,6 +65,38 @@ def validation_errors ].compact end + def exit_page_heading(**args) + return exit_page&.heading(**args) if exit_page.present? + + super(**args) + end + + def exit_page_markdown(**args) + return exit_page&.markdown(**args) if exit_page.present? + + super(**args) + end + + def exit_page_heading=(value, **args) + if exit_page.present? + exit_page.update!(heading: value, **args) + else + attributes[:exit_page] = ExitPage.new(heading: value, question_page: routing_page, **args) + end + + super(value, **args) + end + + def exit_page_markdown=(value, **args) + if exit_page.present? + exit_page.update!(markdown: value, **args) + else + attributes[:exit_page] = ExitPage.new(markdown: value, question_page: routing_page, **args) + end + + super(value, **args) + end + def warning_goto_page_doesnt_exist return nil if is_exit_page? # goto_page_id isn't needed if the route is skipping to the end of the form diff --git a/spec/models/condition_spec.rb b/spec/models/condition_spec.rb index b121be1340..ed81464532 100644 --- a/spec/models/condition_spec.rb +++ b/spec/models/condition_spec.rb @@ -720,4 +720,28 @@ end end end + + describe "using the new ExitPage model" do + describe "#exit_page_heading" do + let(:condition) { create :condition, exit_page_heading: "English heading", exit_page_heading_cy: "Welsh heading" } + + it "returns the heading" do + expect(condition.exit_page_heading).to eq("English heading") + end + + context "when the condition has an ExitPage" do + before do + condition.exit_page = create :exit_page, question_page: condition.routing_page, heading: "Exit page english heading", heading_cy: "Exit page welsh heading" + end + + it "returns the heading from the ExitPage" do + expect(condition.exit_page_heading).to eq("Exit page english heading") + end + + it "returns the welsh heading if the condition an ExitPage" do + expect(condition.exit_page_heading_cy).to eq("Exit page welsh heading") + end + end + end + end end From ca0d763bb54e43aa81da389c2343fae57c8ed1f7 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Mon, 6 Jul 2026 16:46:29 +0100 Subject: [PATCH 04/17] Fix heading/markdown setters for welsh I think this ensures the arg is passed on to the setters. --- app/models/condition.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index dfb0540ba4..904165bd1c 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -79,7 +79,7 @@ def exit_page_markdown(**args) def exit_page_heading=(value, **args) if exit_page.present? - exit_page.update!(heading: value, **args) + exit_page.send(:heading=, value, **args) else attributes[:exit_page] = ExitPage.new(heading: value, question_page: routing_page, **args) end @@ -89,7 +89,7 @@ def exit_page_heading=(value, **args) def exit_page_markdown=(value, **args) if exit_page.present? - exit_page.update!(markdown: value, **args) + exit_page.send(:markdown=, value, **args) else attributes[:exit_page] = ExitPage.new(markdown: value, question_page: routing_page, **args) end From 1350a26543463ca63667fbb8165db8f3384c1b24 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Mon, 6 Jul 2026 16:47:05 +0100 Subject: [PATCH 05/17] Add accepts_nested_attributes_for to Condition model Try using accepts_nested_attributes_for to ensure exit_page is updated when the condition is updated. --- app/models/condition.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/condition.rb b/app/models/condition.rb index 904165bd1c..bc3d5c0e6c 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -11,6 +11,9 @@ class Condition < ApplicationRecord has_one :form, through: :routing_page belongs_to :exit_page, optional: true + accepts_nested_attributes_for :exit_page, update_only: true + validates_associated :exit_page + before_destroy :destroy_postconditions translates :exit_page_heading From 68d66d6cd0bf4d80b98db2a16a056cdb1b34d77a Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Thu, 23 Jul 2026 10:31:47 +0100 Subject: [PATCH 06/17] WIP: Fixing the writing of exit pages --- app/models/condition.rb | 10 ++++++++-- app/models/exit_page.rb | 6 +++--- spec/components/page_list_component/view_spec.rb | 2 +- spec/factories/models/conditions.rb | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index bc3d5c0e6c..1b338b3e7f 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -84,7 +84,10 @@ def exit_page_heading=(value, **args) if exit_page.present? exit_page.send(:heading=, value, **args) else - attributes[:exit_page] = ExitPage.new(heading: value, question_page: routing_page, **args) + exit_page = ExitPage.new(question_page_id: routing_page_id) + exit_page.send(:heading=, value, **args) + exit_page.save! + attributes[:exit_page] = exit_page end super(value, **args) @@ -94,7 +97,10 @@ def exit_page_markdown=(value, **args) if exit_page.present? exit_page.send(:markdown=, value, **args) else - attributes[:exit_page] = ExitPage.new(markdown: value, question_page: routing_page, **args) + exit_page = ExitPage.new(question_page_id: routing_page_id) + exit_page.send(:markdown=, value, **args) + exit_page.save! + attributes[:exit_page] = exit_page end super(value, **args) diff --git a/app/models/exit_page.rb b/app/models/exit_page.rb index 3fe739775c..ac82a2d5d1 100644 --- a/app/models/exit_page.rb +++ b/app/models/exit_page.rb @@ -1,9 +1,9 @@ class ExitPage < ApplicationRecord extend Mobility - belongs_to :question_page, class_name: "Page", optional: false + belongs_to :question_page, class_name: "Page", optional: true - validates :heading, presence: true - validates :markdown, presence: true + validates :heading, presence: true, on: :complete + validates :markdown, presence: true, on: :complete translates :heading, :markdown diff --git a/spec/components/page_list_component/view_spec.rb b/spec/components/page_list_component/view_spec.rb index a340e8b300..928fc97f73 100644 --- a/spec/components/page_list_component/view_spec.rb +++ b/spec/components/page_list_component/view_spec.rb @@ -112,7 +112,7 @@ end context "when the condition has an exit page heading" do - let!(:condition) { create :condition, :with_exit_page, routing_page_id: pages.first.id, check_page_id: pages.first.id, answer_value: "Option 1" } + let!(:condition) { create :condition, :with_exit_page, routing_page: pages.first, check_page_id: pages.first.id, answer_value: "Option 1" } before do render_inline(page_list_component) diff --git a/spec/factories/models/conditions.rb b/spec/factories/models/conditions.rb index 593a4e2fe6..b24d27a9ce 100644 --- a/spec/factories/models/conditions.rb +++ b/spec/factories/models/conditions.rb @@ -13,8 +13,8 @@ exit_page_markdown { nil } # Define the association but we want to set it to nil by default - association :exit_page - exit_page_id { nil } + # association :exit_page + exit_page { nil } trait :with_exit_page do goto_page { nil } From ed79fac7f59483e05a07361fd1cef46ac5f8ddd5 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Thu, 23 Jul 2026 14:52:00 +0100 Subject: [PATCH 07/17] Add Condition#sync_exit_pages Add new method Condition#sync_exit_pages to sync the exit page. Fix some other issues --- app/models/condition.rb | 39 ++++++++++----------------------------- app/models/exit_page.rb | 6 +++--- app/models/page.rb | 2 +- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index 1b338b3e7f..bd1a092102 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -20,43 +20,34 @@ class Condition < ApplicationRecord translates :exit_page_markdown, presence: false # Without presence here, the value is nil if set to "" def self.create_and_update_form!(**args) - exit_page_attributes = args.slice(:exit_page_heading, :exit_page_markdown) - condition = Condition.new(**args) - if exit_page_attributes.present? - heading = exit_page_attributes.fetch(:exit_page_heading, "") - markdown = exit_page_attributes.fetch(:exit_page_markdown, "") - condition.exit_page = ExitPage.create(heading:, markdown:, question_page: condition.routing_page) - end - condition.save_and_update_form condition end - def save_and_update_form(**args) - exit_page_attributes = args.slice(:exit_page_heading, :exit_page_markdown) - - if exit_page_attributes.present? - heading = exit_page_attributes.fetch(:exit_page_heading, "") - markdown = exit_page_attributes.fetch(:exit_page_markdown, "") - + def sync_exit_pages + if exit_page_markdown.present? if exit_page.present? - exit_page.update!(heading:, markdown:) + exit_page.update!(heading: exit_page_heading, markdown: exit_page_markdown, question_page: routing_page) else - ExitPage.create!(heading:, markdown:, question_page: routing_page) + ExitPage.create!(heading: exit_page_heading, markdown: exit_page_markdown, question_page: routing_page) end + elsif exit_page.present? + exit_page.destroy! end + end + def save_and_update_form(**args) save! + sync_exit_pages form.save_question_changes! end def destroy_and_update_form! exit_page_to_delete = exit_page destroy! && form.save_question_changes! - - exit_page_to_delete.presence&.destroy! unless FeatureService.new(group: form.group).enabled?(:multiple_branches) + exit_page_to_delete.presence&.destroy! # unless FeatureService.new(group: form.group).enabled?(:multiple_branches) end def validation_errors @@ -83,11 +74,6 @@ def exit_page_markdown(**args) def exit_page_heading=(value, **args) if exit_page.present? exit_page.send(:heading=, value, **args) - else - exit_page = ExitPage.new(question_page_id: routing_page_id) - exit_page.send(:heading=, value, **args) - exit_page.save! - attributes[:exit_page] = exit_page end super(value, **args) @@ -96,11 +82,6 @@ def exit_page_heading=(value, **args) def exit_page_markdown=(value, **args) if exit_page.present? exit_page.send(:markdown=, value, **args) - else - exit_page = ExitPage.new(question_page_id: routing_page_id) - exit_page.send(:markdown=, value, **args) - exit_page.save! - attributes[:exit_page] = exit_page end super(value, **args) diff --git a/app/models/exit_page.rb b/app/models/exit_page.rb index ac82a2d5d1..3fe739775c 100644 --- a/app/models/exit_page.rb +++ b/app/models/exit_page.rb @@ -1,9 +1,9 @@ class ExitPage < ApplicationRecord extend Mobility - belongs_to :question_page, class_name: "Page", optional: true + belongs_to :question_page, class_name: "Page", optional: false - validates :heading, presence: true, on: :complete - validates :markdown, presence: true, on: :complete + validates :heading, presence: true + validates :markdown, presence: true translates :heading, :markdown diff --git a/app/models/page.rb b/app/models/page.rb index 7928806a33..079d2ca39c 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -59,8 +59,8 @@ def save_and_update_form form.save_question_changes! if answer_type_changed_from_selection_only_one_option || answer_settings_changed_from_only_one_option - exit_pages.destroy_all check_conditions.destroy_all + exit_pages.destroy_all end end From 4f05dc2cc2d7bc93be3e66943c40d5e08604abcd Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Thu, 23 Jul 2026 15:39:35 +0100 Subject: [PATCH 08/17] Remove unused accepts_nested)_attributes --- app/models/condition.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index bd1a092102..0dd05967f4 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -11,9 +11,6 @@ class Condition < ApplicationRecord has_one :form, through: :routing_page belongs_to :exit_page, optional: true - accepts_nested_attributes_for :exit_page, update_only: true - validates_associated :exit_page - before_destroy :destroy_postconditions translates :exit_page_heading From ef55184b5110f5c964f1127d6e2bb5375b994449 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 10:55:52 +0100 Subject: [PATCH 09/17] WIP: Add before_save to Condition --- app/models/condition.rb | 50 +++++++++++++++++++---------------- spec/models/condition_spec.rb | 25 +++++++++++++++++- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index 0dd05967f4..5f68fba1a7 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -9,9 +9,11 @@ class Condition < ApplicationRecord belongs_to :goto_page, class_name: "Page", optional: true has_one :form, through: :routing_page - belongs_to :exit_page, optional: true + belongs_to :exit_page, optional: true, autosave: true + validates_associated :exit_page before_destroy :destroy_postconditions + before_save :sync_exit_page translates :exit_page_heading translates :exit_page_markdown, presence: false # Without presence here, the value is nil if set to "" @@ -23,21 +25,23 @@ def self.create_and_update_form!(**args) condition end - def sync_exit_pages - if exit_page_markdown.present? - if exit_page.present? - exit_page.update!(heading: exit_page_heading, markdown: exit_page_markdown, question_page: routing_page) - else - ExitPage.create!(heading: exit_page_heading, markdown: exit_page_markdown, question_page: routing_page) - end - elsif exit_page.present? - exit_page.destroy! + def sync_exit_page + if exit_page_heading.present? || exit_page_markdown.present? + build_exit_page(question_page: routing_page) if exit_page.nil? + + exit_page.heading = exit_page_heading + exit_page.markdown = exit_page_markdown + exit_page.question_page = routing_page + end + + if exit_page_heading.blank? || exit_page_markdown.blank? + exit_page&.destroy! + self.exit_page = nil end end def save_and_update_form(**args) save! - sync_exit_pages form.save_question_changes! end @@ -68,21 +72,21 @@ def exit_page_markdown(**args) super(**args) end - def exit_page_heading=(value, **args) - if exit_page.present? - exit_page.send(:heading=, value, **args) - end + # def exit_page_heading=(value, **args) + # if exit_page.present? + # exit_page.send(:heading=, value, **args) + # end - super(value, **args) - end + # super(value, **args) + # end - def exit_page_markdown=(value, **args) - if exit_page.present? - exit_page.send(:markdown=, value, **args) - end + # def exit_page_markdown=(value, **args) + # if exit_page.present? + # exit_page.send(:markdown=, value, **args) + # end - super(value, **args) - end + # super(value, **args) + # end def warning_goto_page_doesnt_exist return nil if is_exit_page? diff --git a/spec/models/condition_spec.rb b/spec/models/condition_spec.rb index ed81464532..e289840432 100644 --- a/spec/models/condition_spec.rb +++ b/spec/models/condition_spec.rb @@ -723,7 +723,7 @@ describe "using the new ExitPage model" do describe "#exit_page_heading" do - let(:condition) { create :condition, exit_page_heading: "English heading", exit_page_heading_cy: "Welsh heading" } + let(:condition) { create :condition, exit_page_heading: "English heading", exit_page_markdown: "English markdown", exit_page_heading_cy: "Welsh heading", exit_page_markdown_cy: "Welsh markdown" } it "returns the heading" do expect(condition.exit_page_heading).to eq("English heading") @@ -742,6 +742,29 @@ expect(condition.exit_page_heading_cy).to eq("Exit page welsh heading") end end + + describe "changing the heading" do + let(:new_heading) { "New heading" } + + it "updates the heading" do + condition.exit_page_heading = new_heading + expect(condition.exit_page_heading).to eq(new_heading) + end + + it "updates the ExitPage model" do + condition.exit_page_heading = new_heading + + condition.save! + expect(condition.exit_page.heading).to eq(new_heading) + end + + it "changing the welsh heading, changes the ExitPage welsh heading" do + condition.exit_page_heading_cy = "welsh heading" + debugger + condition.save! + expect(condition.exit_page.reload.heading_cy).to eq("welsh heading") + end + end end end end From 0558fc598c324335b549854256919f8e6c16564b Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:45:40 +0100 Subject: [PATCH 10/17] Add nullify to exit_page conditions --- app/models/exit_page.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/exit_page.rb b/app/models/exit_page.rb index 3fe739775c..82ac0e5c74 100644 --- a/app/models/exit_page.rb +++ b/app/models/exit_page.rb @@ -1,6 +1,7 @@ class ExitPage < ApplicationRecord extend Mobility belongs_to :question_page, class_name: "Page", optional: false + has_many :conditions, class_name: "Condition", dependent: :nullify validates :heading, presence: true validates :markdown, presence: true From 9ae165b10dc666c6f8bf08963caa63c9b45db86a Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:45:57 +0100 Subject: [PATCH 11/17] Add Form#exit_pages association --- app/models/form.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/form.rb b/app/models/form.rb index 3ab473a3b7..38f42ac480 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -17,6 +17,7 @@ class Form < ApplicationRecord has_one :draft_welsh_form_document, -> { where tag: "draft", language: :cy }, class_name: "FormDocument" has_one :draft_form_document, -> { where tag: "draft", language: :en }, class_name: "FormDocument" has_many :conditions, through: :pages, source: :routing_conditions + has_many :exit_pages, through: :pages, source: :exit_pages has_many :delivery_configurations, dependent: :destroy has_one :immediate_email_delivery_configuration, -> { where delivery_method: "email", delivery_schedule: "immediate" }, class_name: "DeliveryConfiguration" has_one :s3_delivery_configuration, -> { where delivery_method: "s3", delivery_schedule: "immediate" }, class_name: "DeliveryConfiguration" From 7d1941a80beab997a0c37edd25c9804fd166ad0b Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:46:14 +0100 Subject: [PATCH 12/17] Use sync_exit_page to remove ExitPage We don't need to explicitly destroy the ExitPage, as it will be destroyed when the Condition updated. diff --git a/app/models/condition.rb b/app/models/condition.rb index 30a1250a2..dfb0540ba 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -16,19 +16,44 @@ class Condition < ApplicationRecord translates :exit_page_heading translates :exit_page_markdown, presence: false # Without presence here, the value is nil if set to "" --- app/input_objects/pages/conditions_input.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/input_objects/pages/conditions_input.rb b/app/input_objects/pages/conditions_input.rb index a72ff5ced4..0c6de0b566 100644 --- a/app/input_objects/pages/conditions_input.rb +++ b/app/input_objects/pages/conditions_input.rb @@ -32,9 +32,6 @@ def update_condition record.skip_to_end = skip_to_end record.goto_page_id = goto_page_id - exit_page_to_delete = record&.exit_page - record.exit_page_id = nil - exit_page_to_delete.destroy! if exit_page_to_delete record.exit_page_heading = nil record.exit_page_markdown = nil end From 738f7b24e1fa5cb548634b69e4bbac42d2f5be11 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:47:09 +0100 Subject: [PATCH 13/17] Change Condition sync exit pages --- app/models/condition.rb | 77 +++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/app/models/condition.rb b/app/models/condition.rb index 5f68fba1a7..a086d18903 100644 --- a/app/models/condition.rb +++ b/app/models/condition.rb @@ -13,33 +13,40 @@ class Condition < ApplicationRecord validates_associated :exit_page before_destroy :destroy_postconditions - before_save :sync_exit_page + before_validation :sync_exit_page translates :exit_page_heading translates :exit_page_markdown, presence: false # Without presence here, the value is nil if set to "" - def self.create_and_update_form!(**args) - condition = Condition.new(**args) - - condition.save_and_update_form - condition - end + alias_method :legacy_exit_page_heading, :exit_page_heading + alias_method :legacy_exit_page_heading=, :exit_page_heading= + alias_method :legacy_exit_page_heading_cy, :exit_page_heading_cy + alias_method :legacy_exit_page_markdown, :exit_page_markdown + alias_method :legacy_exit_page_markdown=, :exit_page_markdown= + alias_method :legacy_exit_page_markdown_cy, :exit_page_markdown_cy def sync_exit_page - if exit_page_heading.present? || exit_page_markdown.present? - build_exit_page(question_page: routing_page) if exit_page.nil? + if legacy_exit_page_heading.present? && legacy_exit_page_markdown.present? + self.exit_page ||= build_exit_page(question_page: routing_page) - exit_page.heading = exit_page_heading - exit_page.markdown = exit_page_markdown - exit_page.question_page = routing_page + exit_page.heading = legacy_exit_page_heading + exit_page.markdown = legacy_exit_page_markdown + exit_page.heading_cy = legacy_exit_page_heading_cy + exit_page.markdown_cy = legacy_exit_page_markdown_cy end - if exit_page_heading.blank? || exit_page_markdown.blank? - exit_page&.destroy! - self.exit_page = nil + if legacy_exit_page_heading.blank? || legacy_exit_page_markdown.blank? + exit_page&.mark_for_destruction end end + def self.create_and_update_form!(**args) + condition = Condition.new(**args) + + condition.save_and_update_form + condition + end + def save_and_update_form(**args) save! form.save_question_changes! @@ -60,17 +67,17 @@ def validation_errors ].compact end - def exit_page_heading(**args) - return exit_page&.heading(**args) if exit_page.present? + # def exit_page_heading(**args) + # return exit_page&.heading(**args) if exit_page.present? - super(**args) - end + # super(**args) + # end - def exit_page_markdown(**args) - return exit_page&.markdown(**args) if exit_page.present? + # def exit_page_markdown(**args) + # return exit_page&.markdown(**args) if exit_page.present? - super(**args) - end + # super(**args) + # end # def exit_page_heading=(value, **args) # if exit_page.present? @@ -88,6 +95,30 @@ def exit_page_markdown(**args) # super(value, **args) # end + def exit_page_heading(locale: nil, **options) + exit_page&.heading(locale:, **options) || legacy_exit_page_heading(locale:, **options) + end + + def exit_page_heading=(value, locale: nil, **options) + public_send(:legacy_exit_page_heading=, value, locale: locale, **options) + end + + def exit_page_heading_cy + exit_page&.heading_cy || legacy_exit_page_heading_cy + end + + def exit_page_markdown(locale: nil, **options) + exit_page&.markdown(locale:, **options) || legacy_exit_page_markdown(locale:, **options) + end + + def exit_page_markdown=(value, locale: nil, **options) + public_send(:legacy_exit_page_markdown=, value, locale: locale, **options) + end + + def exit_page_markdown_cy + exit_page&.markdown_cy || legacy_exit_page_markdown_cy + end + def warning_goto_page_doesnt_exist return nil if is_exit_page? # goto_page_id isn't needed if the route is skipping to the end of the form From fee609c14553f43aa6217a16e5c2ae4126521274 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:47:31 +0100 Subject: [PATCH 14/17] Ensure ExitPage::Translations are deleted when reverting a draft --- app/services/revert_draft_form_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/revert_draft_form_service.rb b/app/services/revert_draft_form_service.rb index 49c1b8f7e0..065adefec2 100644 --- a/app/services/revert_draft_form_service.rb +++ b/app/services/revert_draft_form_service.rb @@ -188,6 +188,7 @@ def clear_welsh_translations form.translations.where(locale: :cy).delete_all Page::Translation.where(locale: :cy, page_id: form.page_ids).delete_all Condition::Translation.where(locale: :cy, condition_id: form.condition_ids).delete_all + ExitPage::Translation.where(locale: :cy, exit_page_id: form.exit_page_ids).delete_all end def revert_delivery_configurations(form_document_content) From 61b3e11c5e4c1e99aded8f933b2525bfca1ab66c Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:47:59 +0100 Subject: [PATCH 15/17] Fix page spec which built invalid ExitPages --- spec/models/page_spec.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/spec/models/page_spec.rb b/spec/models/page_spec.rb index 918c67117d..cbb5cc405f 100644 --- a/spec/models/page_spec.rb +++ b/spec/models/page_spec.rb @@ -481,13 +481,12 @@ end context "when page has routing conditions and ExitPages" do - let(:routing_conditions) { [create(:condition, exit_page:)] } + let(:routing_conditions) { [create(:condition)] } let(:check_conditions) { routing_conditions } - let(:exit_page) { create :exit_page } + let(:exit_page) { create :exit_page, question_page: page } before do - exit_page.question_page = page - exit_page.save! + routing_conditions.first.exit_page = exit_page end it "does not delete existing conditions or ExitPages" do @@ -792,14 +791,10 @@ end context "when the page has an ExitPage" do - let!(:exit_page) { create :exit_page, question_page: page } - - before do - create(:condition, routing_page_id: page.id, check_page_id: page.id, exit_page:) - end + let!(:condition) { create(:condition, :with_exit_page, routing_page_id: page.id, check_page_id: page.id) } it "includes the exit page" do - expect(page.reload.as_form_document_step(second_page)["exit_pages"].first).to match a_hash_including("id" => exit_page.id, "markdown" => exit_page.markdown, "heading" => exit_page.heading) + expect(page.reload.as_form_document_step(second_page)["exit_pages"].first).to match a_hash_including("id" => condition.exit_page_id, "markdown" => condition.exit_page.markdown, "heading" => condition.exit_page.heading) end end end From 9a103819d83b92af16bb74b138178ee92c52201a Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:48:23 +0100 Subject: [PATCH 16/17] Remove debug statment --- spec/models/condition_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/condition_spec.rb b/spec/models/condition_spec.rb index e289840432..0e981fcc36 100644 --- a/spec/models/condition_spec.rb +++ b/spec/models/condition_spec.rb @@ -760,7 +760,6 @@ it "changing the welsh heading, changes the ExitPage welsh heading" do condition.exit_page_heading_cy = "welsh heading" - debugger condition.save! expect(condition.exit_page.reload.heading_cy).to eq("welsh heading") end From 25f80015ca5058fdc20514a5a6530e46ff782d76 Mon Sep 17 00:00:00 2001 From: Thomas Iles Date: Fri, 24 Jul 2026 16:49:06 +0100 Subject: [PATCH 17/17] Remove exit_page from Condition factory --- spec/factories/models/conditions.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/factories/models/conditions.rb b/spec/factories/models/conditions.rb index b24d27a9ce..a7e582d1e0 100644 --- a/spec/factories/models/conditions.rb +++ b/spec/factories/models/conditions.rb @@ -12,10 +12,6 @@ exit_page_heading { nil } exit_page_markdown { nil } - # Define the association but we want to set it to nil by default - # association :exit_page - exit_page { nil } - trait :with_exit_page do goto_page { nil } exit_page_heading { "Exit page heading" }