From 6ad96c44d04df01e6abde097c681e824dd5fe745 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Wed, 8 Jul 2026 17:31:15 +0200 Subject: Fix authorship and published_at loss in autosave promotion Two real bugs surfaced by the full test suite, not by the new tests written for this feature -- both were latent the moment lock_for_editing! and save_draft! replaced find_or_create_draft, and only visible once an existing test exercised the exact path each one lived in. lock_for_editing! never stamped user/editor onto a draft that already existed when the lock was acquired. find_or_create_draft used to do this as part of acquiring the lock; splitting lock acquisition from draft creation dropped it entirely, since it looked like draft-creation logic rather than locking logic. Restored as an explicit step: claim authorship only if none is set yet, editorship unconditionally, matching the old behavior exactly. save_draft!'s "no draft yet" branch set user/editor before calling clone_attributes_from, whose first line is an unconditional self.reload -- silently discarding both, since neither had been persisted yet. clone_attributes_from also always copies published_at from its source without the ||= guard used for template_name, and the source here is always the autosave, whose published_at is never anything but nil -- meaning every single promotion, not just the first, was quietly resetting a published page's published_at, which publish_draft!'s own ||= Time.now would then treat as never-published and re-stamp. Fixed by running clone_attributes_from first on both branches, then applying user/editor/published_at afterward, exactly as the existing-draft branch already happened to do by accident. Four controller tests updated to insert a real put :update between get :edit and assertions that used to be true immediately after visiting edit -- deferred draft creation means edit alone no longer produces one, which is the intended consequence of this whole redesign, not something these tests were meant to catch. --- app/models/node.rb | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'app/models/node.rb') diff --git a/app/models/node.rb b/app/models/node.rb index 9eb0fe4..f15c908 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -56,6 +56,11 @@ class Node < ApplicationRecord def lock_for_editing! current_user if self.lock_owner.nil? || self.lock_owner == current_user lock_for! current_user + if self.draft + self.draft.user = current_user if self.draft.user.nil? + self.draft.editor = current_user + self.draft.save! + end self else raise( @@ -95,15 +100,18 @@ class Node < ApplicationRecord return unless self.autosave if self.draft + preserved_published_at = self.draft.published_at self.draft.clone_attributes_from self.autosave + self.draft.published_at = preserved_published_at self.draft.user_id = self.autosave.user_id if self.autosave.user_id self.draft.editor = current_user self.draft.save! else - empty_page = self.pages.create! - empty_page.user = self.autosave.user_id ? self.autosave.user : (self.head ? self.head.user : current_user) - empty_page.editor = current_user + empty_page = self.pages.create! empty_page.clone_attributes_from self.autosave + empty_page.user = self.autosave.user_id ? self.autosave.user : (self.head ? self.head.user : current_user) + empty_page.editor = current_user + empty_page.published_at = self.head.published_at if self.head empty_page.save! self.draft = empty_page self.save! @@ -150,6 +158,28 @@ class Node < ApplicationRecord self.draft.reload end + # Discards exactly the topmost non-empty layer -- autosave if present, + # else draft -- and reveals whatever's beneath it. Releases the lock + # only once nothing is left to protect (no draft survives); leaves it + # alone whenever a draft remains, since #edit still has real content + # open. + def revert! current_user + assert_locked_by! current_user + + if self.autosave + self.autosave.destroy + self.autosave_id = nil + self.save! + elsif self.draft && self.head + self.draft.destroy + self.draft_id = nil + self.save! + end + + self.unlock! unless self.draft + self.reload + end + def staged_slug=(value) if head.blank? self.slug = value -- cgit v1.3