diff options
Diffstat (limited to 'app/models/node.rb')
| -rw-r--r-- | app/models/node.rb | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/app/models/node.rb b/app/models/node.rb index dc69508..a2e9981 100644 --- a/app/models/node.rb +++ b/app/models/node.rb | |||
| @@ -19,6 +19,7 @@ class Node < ApplicationRecord | |||
| 19 | before_save :check_for_changed_slug | 19 | before_save :check_for_changed_slug |
| 20 | after_save :update_unique_names_of_children | 20 | after_save :update_unique_names_of_children |
| 21 | before_destroy :refuse_destroy_with_children | 21 | before_destroy :refuse_destroy_with_children |
| 22 | before_destroy :refuse_destroying_trash_node | ||
| 22 | 23 | ||
| 23 | # Validations | 24 | # Validations |
| 24 | validates_length_of :slug, :within => 1..255, :unless => -> { parent_id.nil? || slug.blank? } | 25 | validates_length_of :slug, :within => 1..255, :unless => -> { parent_id.nil? || slug.blank? } |
| @@ -26,6 +27,8 @@ class Node < ApplicationRecord | |||
| 26 | validates_uniqueness_of :slug, :scope => :parent_id, :unless => -> { parent_id.nil? } | 27 | validates_uniqueness_of :slug, :scope => :parent_id, :unless => -> { parent_id.nil? } |
| 27 | validates_presence_of :parent_id, :unless => -> { Node.root.nil? } | 28 | validates_presence_of :parent_id, :unless => -> { Node.root.nil? } |
| 28 | 29 | ||
| 30 | validate :reserved_slug_stays_reserved | ||
| 31 | validate :no_head_inside_trash | ||
| 29 | validates :default_template_name, | 32 | validates :default_template_name, |
| 30 | :inclusion => { :in => ->(_) { Page.custom_templates } }, | 33 | :inclusion => { :in => ->(_) { Page.custom_templates } }, |
| 31 | :allow_nil => true, | 34 | :allow_nil => true, |
| @@ -57,6 +60,15 @@ class Node < ApplicationRecord | |||
| 57 | nil | 60 | nil |
| 58 | end | 61 | end |
| 59 | 62 | ||
| 63 | # The Trash container node. Lazily self-creating and idempotent, so | ||
| 64 | # every environment acquires it on first touch. | ||
| 65 | # Never call from validations. the positional predicates below | ||
| 66 | # exist for that. | ||
| 67 | def self.trash | ||
| 68 | root.children.find_by(:slug => CccConventions::TRASH_SLUG) || | ||
| 69 | root.children.create!(:slug => CccConventions::TRASH_SLUG) | ||
| 70 | end | ||
| 71 | |||
| 60 | # Instance Methods | 72 | # Instance Methods |
| 61 | 73 | ||
| 62 | # Acquires (or reaffirms) the editing lock without creating a draft or | 74 | # Acquires (or reaffirms) the editing lock without creating a draft or |
| @@ -203,6 +215,10 @@ class Node < ApplicationRecord | |||
| 203 | # Return nil if nothing to publish and no staged changes | 215 | # Return nil if nothing to publish and no staged changes |
| 204 | return nil unless self.draft || staged_slug || staged_parent_id | 216 | return nil unless self.draft || staged_slug || staged_parent_id |
| 205 | 217 | ||
| 218 | if in_trash? || trash_node? | ||
| 219 | raise ActiveRecord::RecordInvalid.new(self), "Cannot publish a node in the Trash" | ||
| 220 | end | ||
| 221 | |||
| 206 | path_before = self.unique_name | 222 | path_before = self.unique_name |
| 207 | 223 | ||
| 208 | ActiveRecord::Base.transaction do | 224 | ActiveRecord::Base.transaction do |
| @@ -322,6 +338,20 @@ class Node < ApplicationRecord | |||
| 322 | autosave || draft || head | 338 | autosave || draft || head |
| 323 | end | 339 | end |
| 324 | 340 | ||
| 341 | def trash_node? | ||
| 342 | parent&.root? && slug == CccConventions::TRASH_SLUG | ||
| 343 | end | ||
| 344 | |||
| 345 | # Inside the Trash subtree. False for the Trash node itself. | ||
| 346 | def in_trash? | ||
| 347 | current = parent | ||
| 348 | while current | ||
| 349 | return true if current.trash_node? | ||
| 350 | current = current.parent | ||
| 351 | end | ||
| 352 | false | ||
| 353 | end | ||
| 354 | |||
| 325 | # Returns immutable node id for all new nodes so that the atom feed entry ids | 355 | # Returns immutable node id for all new nodes so that the atom feed entry ids |
| 326 | # stay the same eventhough the slug or positions changes. | 356 | # stay the same eventhough the slug or positions changes. |
| 327 | # Can be removed after a year or so ;) | 357 | # Can be removed after a year or so ;) |
| @@ -435,4 +465,36 @@ class Node < ApplicationRecord | |||
| 435 | end | 465 | end |
| 436 | end | 466 | end |
| 437 | end | 467 | end |
| 468 | |||
| 469 | private | ||
| 470 | |||
| 471 | def reserved_slug_stays_reserved | ||
| 472 | if parent&.root? && !trash_node_already_me? | ||
| 473 | errors.add(:slug, "is reserved for the Trash") if slug == CccConventions::TRASH_SLUG | ||
| 474 | errors.add(:staged_slug, "is reserved for the Trash") if staged_slug == CccConventions::TRASH_SLUG | ||
| 475 | end | ||
| 476 | |||
| 477 | if persisted? && slug_was == CccConventions::TRASH_SLUG && Node.find(id).trash_node? | ||
| 478 | errors.add(:slug, "of the Trash node cannot change") if slug_changed? | ||
| 479 | errors.add(:parent_id, "of the Trash node cannot change") if parent_id_changed? | ||
| 480 | errors.add(:staged_slug, "must stay empty on the Trash node") if staged_slug.present? | ||
| 481 | errors.add(:staged_parent_id, "must stay empty on the Trash node") if staged_parent_id.present? | ||
| 482 | end | ||
| 483 | end | ||
| 484 | |||
| 485 | def trash_node_already_me? | ||
| 486 | existing = Node.root&.children&.find_by(:slug => CccConventions::TRASH_SLUG) | ||
| 487 | existing.nil? || existing.id == id | ||
| 488 | end | ||
| 489 | |||
| 490 | def no_head_inside_trash | ||
| 491 | return unless head_id.present? | ||
| 492 | errors.add(:head_id, "cannot exist inside the Trash") if in_trash? || trash_node? | ||
| 493 | end | ||
| 494 | |||
| 495 | def refuse_destroying_trash_node | ||
| 496 | return unless trash_node? | ||
| 497 | errors.add(:base, "The Trash node cannot be destroyed") | ||
| 498 | throw :abort | ||
| 499 | end | ||
| 438 | end | 500 | end |
