summaryrefslogtreecommitdiff
path: root/app/models/node.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/node.rb')
-rw-r--r--app/models/node.rb43
1 files changed, 37 insertions, 6 deletions
diff --git a/app/models/node.rb b/app/models/node.rb
index 4dae287..8d0756a 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -4,6 +4,7 @@ class Node < ApplicationRecord
4 4
5 # Associations 5 # Associations
6 has_many :pages, -> { order("revision ASC") }, :dependent => :destroy 6 has_many :pages, -> { order("revision ASC") }, :dependent => :destroy
7 has_many :node_actions, :dependent => :nullify
7 belongs_to :head, :class_name => "Page", :foreign_key => :head_id, optional: true 8 belongs_to :head, :class_name => "Page", :foreign_key => :head_id, optional: true
8 belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id, optional: true 9 belongs_to :draft, :class_name => "Page", :foreign_key => :draft_id, optional: true
9 # Autosave pages carry no node_id, so has_many :pages does not cover 10 # Autosave pages carry no node_id, so has_many :pages does not cover
@@ -351,19 +352,49 @@ class Node < ApplicationRecord
351 end 352 end
352 end 353 end
353 354
354 # Final deletion. Only from inside the Trash, never with children. 355 # Final deletion -- only from inside the Trash. Removes the whole
355 # The log entry is written first, in the same transaction; node_id 356 # subtree, deepest first, each node through a real destroy! so every
356 # nullifies when the row dies, and the metadata carries what remains. 357 # per-node cascade runs (the categorical difference from the old
358 # delete_all nuke). refuse_destroy_with_children on bare destroy is
359 # untouched
360 # One log entry at the root, per the subtree rule, written before the
361 # rows die.
357 def destroy_from_trash! current_user = nil 362 def destroy_from_trash! current_user = nil
358 raise ActiveRecord::RecordInvalid.new(self), "Nodes are only destroyed from the Trash" unless in_trash? 363 raise ActiveRecord::RecordInvalid.new(self), "Nodes are only destroyed from the Trash" unless in_trash?
359 364
360 ActiveRecord::Base.transaction do 365 ActiveRecord::Base.transaction do
361 NodeAction.record!(:node => self, :user => current_user, :action => "destroy", 366 doomed = self_and_descendants_ordered_with_level
362 :path => unique_name) 367 .sort_by { |_, level| -level }
363 destroy! 368 .map(&:first)
369
370 metadata = { :path => unique_name }
371 metadata[:destroyed_descendants] = doomed.size - 1 if doomed.size > 1
372
373 NodeAction.record!(:node => self, :user => current_user, :action => "destroy", **metadata)
374 doomed.each(&:destroy!)
364 end 375 end
365 end 376 end
366 377
378 # The most recent trash entry. Its path.from is the restore hint.
379 def last_trash_entry
380 node_actions.where(:action => "trash").order(:occurred_at => :desc, :id => :desc).first
381 end
382
383 # The node's pre-trash parent, if a living node still answers to
384 # that path. Nil when the parent was itself trashed (its unique_name
385 # changed) or deleted; a different node that has since taken over
386 # the path is a legitimate suggestion.
387 def suggested_restore_parent
388 from = last_trash_entry&.metadata&.dig("path", "from")
389 return nil unless from
390
391 parent_path = from.rpartition("/").first
392 return Node.root if parent_path.empty?
393
394 candidate = Node.find_by(:unique_name => parent_path)
395 candidate unless candidate.nil? || candidate.in_trash? || candidate.trash_node?
396 end
397
367 # returns an array with all parts of a unique_name rather than a string 398 # returns an array with all parts of a unique_name rather than a string
368 def unique_path 399 def unique_path
369 unique_name.to_s.split("/") 400 unique_name.to_s.split("/")