summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/node.rb42
-rw-r--r--app/models/related_asset.rb2
-rw-r--r--config/locales/de.yml37
-rw-r--r--config/locales/en.yml30
-rw-r--r--test/controllers/nodes_controller_test.rb2
-rw-r--r--test/models/node_test.rb17
-rw-r--r--test/models/related_asset_test.rb2
7 files changed, 112 insertions, 20 deletions
diff --git a/app/models/node.rb b/app/models/node.rb
index 1e61f9fe..602382fb 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -236,7 +236,8 @@ class Node < ApplicationRecord
236 return nil unless self.draft || staged_slug || staged_parent_id 236 return nil unless self.draft || staged_slug || staged_parent_id
237 237
238 if in_trash? || trash_node? 238 if in_trash? || trash_node?
239 raise ActiveRecord::RecordInvalid.new(self), "Cannot publish a node in the Trash" 239 errors.add(:base, :publish_in_trash)
240 raise ActiveRecord::RecordInvalid.new(self)
240 end 241 end
241 242
242 path_before = self.unique_name 243 path_before = self.unique_name
@@ -265,7 +266,8 @@ class Node < ApplicationRecord
265 new_parent = Node.find(staged_parent_id) 266 new_parent = Node.find(staged_parent_id)
266 267
267 if new_parent == self || self.descendants.include?(new_parent) 268 if new_parent == self || self.descendants.include?(new_parent)
268 raise ActiveRecord::RecordInvalid.new(self), "Cannot move a node under itself or one of its own descendants" 269 errors.add(:base, :move_under_self)
270 raise ActiveRecord::RecordInvalid.new(self)
269 end 271 end
270 272
271 self.staged_parent_id = nil 273 self.staged_parent_id = nil
@@ -316,7 +318,10 @@ class Node < ApplicationRecord
316 # at the root, carrying the leaving-public-view snapshot. 318 # at the root, carrying the leaving-public-view snapshot.
317 def trash! current_user = nil 319 def trash! current_user = nil
318 return nil if in_trash? 320 return nil if in_trash?
319 raise ActiveRecord::RecordInvalid.new(self), "The Trash node itself cannot be trashed" if trash_node? 321 if trash_node?
322 errors.add(:base, :trash_the_trash)
323 raise ActiveRecord::RecordInvalid.new(self)
324 end
320 325
321 ActiveRecord::Base.transaction do 326 ActiveRecord::Base.transaction do
322 path_before = unique_name 327 path_before = unique_name
@@ -360,7 +365,8 @@ class Node < ApplicationRecord
360 365
361 if new_parent.nil? || new_parent == self || descendants.include?(new_parent) || 366 if new_parent.nil? || new_parent == self || descendants.include?(new_parent) ||
362 new_parent.trash_node? || new_parent.in_trash? 367 new_parent.trash_node? || new_parent.in_trash?
363 raise ActiveRecord::RecordInvalid.new(self), "Restore target must be a living node" 368 errors.add(:base, :restore_target_invalid)
369 raise ActiveRecord::RecordInvalid.new(self)
364 end 370 end
365 371
366 ActiveRecord::Base.transaction do 372 ActiveRecord::Base.transaction do
@@ -376,7 +382,7 @@ class Node < ApplicationRecord
376 end 382 end
377 end 383 end
378 384
379 # Final deletion -- only from inside the Trash. Removes the whole 385 # Final deletion, only from inside the Trash. Removes the whole
380 # subtree, deepest first, each node through a real destroy! so every 386 # subtree, deepest first, each node through a real destroy! so every
381 # per-node cascade runs (the categorical difference from the old 387 # per-node cascade runs (the categorical difference from the old
382 # delete_all nuke). refuse_destroy_with_children on bare destroy is 388 # delete_all nuke). refuse_destroy_with_children on bare destroy is
@@ -384,7 +390,10 @@ class Node < ApplicationRecord
384 # One log entry at the root, per the subtree rule, written before the 390 # One log entry at the root, per the subtree rule, written before the
385 # rows die. 391 # rows die.
386 def destroy_from_trash! current_user = nil 392 def destroy_from_trash! current_user = nil
387 raise ActiveRecord::RecordInvalid.new(self), "Nodes are only destroyed from the Trash" unless in_trash? 393 unless in_trash?
394 errors.add(:base, :destroy_outside_trash)
395 raise ActiveRecord::RecordInvalid.new(self)
396 end
388 397
389 ActiveRecord::Base.transaction do 398 ActiveRecord::Base.transaction do
390 doomed = self_and_descendants_ordered_with_level 399 doomed = self_and_descendants_ordered_with_level
@@ -476,7 +485,8 @@ class Node < ApplicationRecord
476 # :headline => nil | :set | :kept_existing | :not_eligible } 485 # :headline => nil | :set | :kept_existing | :not_eligible }
477 def attach_asset! asset, user:, headline: false 486 def attach_asset! asset, user:, headline: false
478 if in_trash? || trash_node? 487 if in_trash? || trash_node?
479 raise ActiveRecord::RecordInvalid.new(self), "Cannot attach assets to a node in the Trash" 488 errors.add(:base, :attach_in_trash)
489 raise ActiveRecord::RecordInvalid.new(self)
480 end 490 end
481 491
482 if lock_owner && lock_owner != user 492 if lock_owner && lock_owner != user
@@ -610,7 +620,7 @@ class Node < ApplicationRecord
610 # The Trash feature will be the ordinary path to deletion. 620 # The Trash feature will be the ordinary path to deletion.
611 def refuse_destroy_with_children 621 def refuse_destroy_with_children
612 return unless children.exists? 622 return unless children.exists?
613 errors.add(:base, "Cannot destroy a node that still has children") 623 errors.add(:base, :has_children)
614 throw :abort 624 throw :abort
615 end 625 end
616 626
@@ -668,15 +678,15 @@ class Node < ApplicationRecord
668 678
669 def reserved_slug_stays_reserved 679 def reserved_slug_stays_reserved
670 if parent&.root? && !trash_node_already_me? 680 if parent&.root? && !trash_node_already_me?
671 errors.add(:slug, "is reserved for the Trash") if slug == CccConventions::TRASH_SLUG 681 errors.add(:slug, :reserved_for_trash) if slug == CccConventions::TRASH_SLUG
672 errors.add(:staged_slug, "is reserved for the Trash") if staged_slug == CccConventions::TRASH_SLUG 682 errors.add(:staged_slug, :reserved_for_trash) if staged_slug == CccConventions::TRASH_SLUG
673 end 683 end
674 684
675 if persisted? && slug_was == CccConventions::TRASH_SLUG && Node.find(id).trash_node? 685 if persisted? && slug_was == CccConventions::TRASH_SLUG && Node.find(id).trash_node?
676 errors.add(:slug, "of the Trash node cannot change") if slug_changed? 686 errors.add(:slug, :trash_immutable) if slug_changed?
677 errors.add(:parent_id, "of the Trash node cannot change") if parent_id_changed? 687 errors.add(:parent_id, :trash_immutable) if parent_id_changed?
678 errors.add(:staged_slug, "must stay empty on the Trash node") if staged_slug.present? 688 errors.add(:staged_slug, :trash_must_be_empty) if staged_slug.present?
679 errors.add(:staged_parent_id, "must stay empty on the Trash node") if staged_parent_id.present? 689 errors.add(:staged_parent_id, :trash_must_be_empty) if staged_parent_id.present?
680 end 690 end
681 end 691 end
682 692
@@ -687,12 +697,12 @@ class Node < ApplicationRecord
687 697
688 def no_head_inside_trash 698 def no_head_inside_trash
689 return unless head_id.present? 699 return unless head_id.present?
690 errors.add(:head_id, "cannot exist inside the Trash") if in_trash? || trash_node? 700 errors.add(:head_id, :inside_trash) if in_trash? || trash_node?
691 end 701 end
692 702
693 def refuse_destroying_trash_node 703 def refuse_destroying_trash_node
694 return unless trash_node? 704 return unless trash_node?
695 errors.add(:base, "The Trash node cannot be destroyed") 705 errors.add(:base, :trash_undeletable)
696 throw :abort 706 throw :abort
697 end 707 end
698end 708end
diff --git a/app/models/related_asset.rb b/app/models/related_asset.rb
index 8f8d49cb..caf8afda 100644
--- a/app/models/related_asset.rb
+++ b/app/models/related_asset.rb
@@ -12,6 +12,6 @@ class RelatedAsset < ApplicationRecord
12 12
13 def headline_only_for_images 13 def headline_only_for_images
14 return unless asset 14 return unless asset
15 errors.add(:headline, "can only be set on image or PDF assets") if headline? && !(asset.image? || asset.pdf?) 15 errors.add(:headline, :images_and_pdfs_only) if headline? && !(asset.image? || asset.pdf?)
16 end 16 end
17end 17end
diff --git a/config/locales/de.yml b/config/locales/de.yml
index e8a65cea..647caf57 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -114,6 +114,41 @@ de:
114 node_id: "Node-ID" 114 node_id: "Node-ID"
115 path: "Pfad" 115 path: "Pfad"
116 title: "Titel" 116 title: "Titel"
117 node:
118 slug: "Slug"
119 staged_slug: "Vorgemerkter Slug"
120 parent_id: "Eltern-Node"
121 staged_parent_id: "Vorgemerkter Eltern-Node"
122 head_id: "Head"
123 related_asset:
124 headline: "Aufmacher"
125
126 errors:
127 models:
128 node:
129 # Attribute messages: full_messages prefixes the human attribute
130 # name, so these read as fragments. Defined at model level rather
131 # than per attribute, because several attributes share a message.
132 reserved_for_trash: "ist für den Papierkorb reserviert"
133 trash_immutable: "des Papierkorb-Nodes kann nicht geändert werden"
134 trash_must_be_empty: "muss auf dem Papierkorb-Node leer bleiben"
135 inside_trash: "kann im Papierkorb nicht existieren"
136 attributes:
137 base:
138 # :base messages stand alone -- no attribute name is prefixed,
139 # so these are whole sentences.
140 has_children: "Ein Node mit Kindern kann nicht gelöscht werden"
141 trash_undeletable: "Der Papierkorb-Node kann nicht gelöscht werden"
142 publish_in_trash: "Ein Node im Papierkorb kann nicht veröffentlicht werden"
143 move_under_self: "Ein Node kann nicht unter sich selbst oder einen seiner Nachfahren verschoben werden"
144 trash_the_trash: "Der Papierkorb-Node selbst kann nicht in den Papierkorb verschoben werden"
145 restore_target_invalid: "Das Wiederherstellungsziel muss ein lebender Node sein"
146 destroy_outside_trash: "Nodes können nur aus dem Papierkorb gelöscht werden"
147 attach_in_trash: "An einen Node im Papierkorb können keine Assets angehängt werden"
148 related_asset:
149 attributes:
150 headline:
151 images_and_pdfs_only: "kann nur auf Bildern oder PDFs gesetzt werden"
117 152
118 tags: 153 tags:
119 index: 154 index:
@@ -639,7 +674,7 @@ de:
639 edit: 674 edit:
640 title: "Menüeintrag bearbeiten" 675 title: "Menüeintrag bearbeiten"
641 title_fields: 676 title_fields:
642 falls_back: "Menu-Title leer lassen, fall-back nach deutsch" 677 falls_back: "Menu-Titel. Leere Titel werden aus deutsch übernommen"
643 678
644 layouts: 679 layouts:
645 application: 680 application:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index da87777d..1b112e3d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -66,6 +66,36 @@ en:
66 node_id: "Node Id" 66 node_id: "Node Id"
67 path: "Path" 67 path: "Path"
68 title: "Title" 68 title: "Title"
69 node:
70 slug: "Slug"
71 staged_slug: "Staged slug"
72 parent_id: "Parent node"
73 staged_parent_id: "Staged parent node"
74 head_id: "Head"
75 related_asset:
76 headline: "Headline"
77
78 errors:
79 models:
80 node:
81 reserved_for_trash: "is reserved for the Trash"
82 trash_immutable: "of the Trash node cannot change"
83 trash_must_be_empty: "must stay empty on the Trash node"
84 inside_trash: "cannot exist inside the Trash"
85 attributes:
86 base:
87 has_children: "Cannot destroy a node that still has children"
88 trash_undeletable: "The Trash node cannot be destroyed"
89 publish_in_trash: "Cannot publish a node in the Trash"
90 move_under_self: "Cannot move a node under itself or one of its own descendants"
91 trash_the_trash: "The Trash node itself cannot be trashed"
92 restore_target_invalid: "Restore target must be a living node"
93 destroy_outside_trash: "Nodes are only destroyed from the Trash"
94 attach_in_trash: "Cannot attach assets to a node in the Trash"
95 related_asset:
96 attributes:
97 headline:
98 images_and_pdfs_only: "can only be set on image or PDF assets"
69 99
70 tags: 100 tags:
71 index: 101 index:
diff --git a/test/controllers/nodes_controller_test.rb b/test/controllers/nodes_controller_test.rb
index d7a3abbd..0b6e65ee 100644
--- a/test/controllers/nodes_controller_test.rb
+++ b/test/controllers/nodes_controller_test.rb
@@ -628,7 +628,7 @@ class NodesControllerTest < ActionController::TestCase
628 test "chapters renders the curated heading" do 628 test "chapters renders the curated heading" do
629 login_as :quentin 629 login_as :quentin
630 get :chapters 630 get :chapters
631 assert_select "h1", "Chapters" 631 assert_select "h1", "Dezentrale"
632 end 632 end
633 633
634 test "sitemap collapses configured paths but leaves others open" do 634 test "sitemap collapses configured paths but leaves others open" do
diff --git a/test/models/node_test.rb b/test/models/node_test.rb
index c5b0d6a2..c1316ea6 100644
--- a/test/models/node_test.rb
+++ b/test/models/node_test.rb
@@ -919,4 +919,21 @@ class NodeTest < ActiveSupport::TestCase
919 node.reload 919 node.reload
920 assert_raises(ActiveRecord::RecordInvalid) { node.publish_draft! } 920 assert_raises(ActiveRecord::RecordInvalid) { node.publish_draft! }
921 end 921 end
922
923 test "refusals localise their message rather than hardcoding it" do
924 trash = Node.trash
925
926 I18n.with_locale(:de) do
927 error = assert_raises(ActiveRecord::RecordInvalid) { trash.trash! }
928 assert_includes error.message,
929 I18n.t("activerecord.errors.models.node.attributes.base.trash_the_trash")
930 end
931
932 I18n.with_locale(:en) do
933 trash.errors.clear
934 error = assert_raises(ActiveRecord::RecordInvalid) { trash.trash! }
935 assert_includes error.message,
936 I18n.t("activerecord.errors.models.node.attributes.base.trash_the_trash")
937 end
938 end
922end 939end
diff --git a/test/models/related_asset_test.rb b/test/models/related_asset_test.rb
index bb86ddbb..ea7c3d35 100644
--- a/test/models/related_asset_test.rb
+++ b/test/models/related_asset_test.rb
@@ -29,7 +29,7 @@ class RelatedAssetTest < ActiveSupport::TestCase
29 29
30 related.headline = true 30 related.headline = true
31 assert_not related.valid? 31 assert_not related.valid?
32 assert_includes related.errors[:headline], "can only be set on image or PDF assets" 32 assert_includes related.errors[:headline], I18n.t("activerecord.errors.models.related_asset.attributes.headline.images_and_pdfs_only")
33 end 33 end
34 34
35 test "the headline validation does not raise when asset is missing" do 35 test "the headline validation does not raise when asset is missing" do