diff options
Diffstat (limited to 'app/models/node.rb')
| -rw-r--r-- | app/models/node.rb | 43 |
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("/") |
