summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-17 22:11:44 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-17 22:11:44 +0200
commit71a95f4ca6460a7c2bc345e8b32dbef03d6dd985 (patch)
tree48ba0ccdc1941343bdb2632ec5724ce38fd9ecfb /app
parente3021c88a0ce35b4c069e148a5d82bb1acbd3c91 (diff)
Refuse destroying nodes that still have children
NestedTree's before_destroy silently delete_all'd the whole subtree, bypassing every per-node cleanup. Nodes are never destroyed recursively; descendants must be removed individually.
Diffstat (limited to 'app')
-rw-r--r--app/models/concerns/nested_tree.rb8
-rw-r--r--app/models/node.rb16
2 files changed, 13 insertions, 11 deletions
diff --git a/app/models/concerns/nested_tree.rb b/app/models/concerns/nested_tree.rb
index 9a2eb0e..befcdb3 100644
--- a/app/models/concerns/nested_tree.rb
+++ b/app/models/concerns/nested_tree.rb
@@ -9,8 +9,6 @@ module NestedTree
9 included do 9 included do
10 belongs_to :parent, class_name: name, foreign_key: :parent_id, optional: true, inverse_of: :children 10 belongs_to :parent, class_name: name, foreign_key: :parent_id, optional: true, inverse_of: :children
11 has_many :children, -> { order(:id) }, class_name: name, foreign_key: :parent_id, inverse_of: :parent 11 has_many :children, -> { order(:id) }, class_name: name, foreign_key: :parent_id, inverse_of: :parent
12
13 before_destroy :delete_descendants
14 end 12 end
15 13
16 class_methods do 14 class_methods do
@@ -77,10 +75,4 @@ module NestedTree
77 def move_to_child_of(new_parent) 75 def move_to_child_of(new_parent)
78 update!(parent_id: new_parent.id) 76 update!(parent_id: new_parent.id)
79 end 77 end
80
81 private
82
83 def delete_descendants
84 self.class.where(id: descendants.pluck(:id)).delete_all
85 end
86end 78end
diff --git a/app/models/node.rb b/app/models/node.rb
index 70ed4da..6ce6591 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -12,9 +12,10 @@ class Node < ApplicationRecord
12 belongs_to :lock_owner, :class_name => "User", :foreign_key => :locking_user_id, optional: true 12 belongs_to :lock_owner, :class_name => "User", :foreign_key => :locking_user_id, optional: true
13 13
14 # Callbacks 14 # Callbacks
15 after_create :initialize_empty_page 15 after_create :initialize_empty_page
16 before_save :check_for_changed_slug 16 before_save :check_for_changed_slug
17 after_save :update_unique_names_of_children 17 after_save :update_unique_names_of_children
18 before_destroy :refuse_destroy_with_children
18 19
19 # Validations 20 # Validations
20 validates_length_of :slug, :within => 1..255, :unless => -> { parent_id.nil? || slug.blank? } 21 validates_length_of :slug, :within => 1..255, :unless => -> { parent_id.nil? || slug.blank? }
@@ -366,6 +367,15 @@ class Node < ApplicationRecord
366 ) 367 )
367 end 368 end
368 369
370 # Nodes are never destroyed recursively
371 # Descendants must be removed or reparented individually first.
372 # The Trash feature will be the ordinary path to deletion.
373 def refuse_destroy_with_children
374 return unless children.exists?
375 errors.add(:base, "Cannot destroy a node that still has children")
376 throw :abort
377 end
378
369 def self.recently_changed 379 def self.recently_changed
370 includes(:head).where( 380 includes(:head).where(
371 "pages.updated_at < ? AND pages.updated_at > ? AND nodes.parent_id IS NOT NULL", 381 "pages.updated_at < ? AND pages.updated_at > ? AND nodes.parent_id IS NOT NULL",