From 4df88b601b1900c287051d827eaff46f498f60d0 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Wed, 19 Aug 2026 16:05:40 +0200 Subject: Redirect a page to a node or an external URL Page#redirect_target resolves the precedence and returns nil for a destination that is restricted or has no head, so a page with a broken target renders itself rather than linking nowhere. The banner partial will call the same method, so the redirect and the link cannot drift. One hop, no exceptions, checked in publish_draft! rather than as a validation: two nodes publishing concurrently could each pass a save-time check and still produce a chain. "Live" means heads only, a draft redirect that has not published is not yet a link anyone can follow. Node.search excludes redirecting pages. editor_search does not: an editor looking for one searches by title, and only the body is worth hiding. --- app/models/node.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'app/models/node.rb') diff --git a/app/models/node.rb b/app/models/node.rb index 6fc6c3dc..52e06d2d 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -236,6 +236,32 @@ class Node < ApplicationRecord raise ActiveRecord::RecordInvalid.new(self) end + if self.draft.redirect.present? + if self.draft.redirect_node_id == self.id + errors.add(:base, :redirect_to_self) + raise ActiveRecord::RecordInvalid.new(self) + end + + if self.draft.redirect_node_id.present? + target = Node.find_by(:id => self.draft.redirect_node_id) + + unless target + errors.add(:base, :redirect_target_missing) + raise ActiveRecord::RecordInvalid.new(self) + end + + if target.head&.redirect.present? + errors.add(:base, :redirect_to_redirect) + raise ActiveRecord::RecordInvalid.new(self) + end + end + + if Page.redirecting_to(self.id).exists? + errors.add(:base, :redirect_would_chain) + raise ActiveRecord::RecordInvalid.new(self) + end + end + path_before = self.unique_name ActiveRecord::Base.transaction do @@ -602,6 +628,7 @@ class Node < ApplicationRecord def self.search(term, _ = {}) joins(head: :translations) .where("page_translations.search_vector @@ plainto_tsquery('simple', ?)", term) + .where(:pages => { :redirect => nil }) .distinct end -- cgit v1.3