summaryrefslogtreecommitdiff
path: root/app/models/asset.rb
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-08-09 03:35:48 +0200
committererdgeist <erdgeist@erdgeist.org>2026-08-09 03:35:48 +0200
commitd16161eab9029a509951146b80334c304c35fd34 (patch)
tree87dd7426389b0e27b31925dc28c76a442eb721b8 /app/models/asset.rb
parent20c735b0c5a2db9b8984848253ad99332d6211a8 (diff)
Refuse destroying an asset that is still attached
Diffstat (limited to 'app/models/asset.rb')
-rw-r--r--app/models/asset.rb45
1 files changed, 12 insertions, 33 deletions
diff --git a/app/models/asset.rb b/app/models/asset.rb
index 4d43b18f..08ef5a78 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -41,44 +41,23 @@ class Asset < ApplicationRecord
41 :ids => page_ids).distinct 41 :ids => page_ids).distinct
42 end 42 end
43 43
44 # An asset's reach is the reach of the pages carrying it: destroying one 44 # Witnessed destruction, refused while the asset is attached to any
45 # removes it from every live page at once, so a single restricted 45 # current row. Detaching stays an in-editor act, so nothing
46 # attachment makes the destruction a restricted act. 46 # destroyed here is carried by a page. The entry is still warranted:
47 def restricted? 47 # the original and its variants are reachable under /system/uploads
48 attached_nodes.any?(&:restricted?) 48 # until the row and its files die.
49 end
50
51 # Witnessed destruction. Destroying an asset is a public-facing act
52 # even when unattached. The original and its variants are publicly
53 # reachable under /system/uploads, so an entry is always written,
54 # before the row and its files die. Every currently-attached node
55 # participates so its zoomed history shows the loss; the asset itself
56 # participates as the first non-Node subject (its participant row
57 # dangles after destroy, by design, the name lives on in metadata).
58 def destroy_witnessed! user: 49 def destroy_witnessed! user:
59 if user && !user.may_change_live?(self) 50 if attached_nodes.any?
60 errors.add(:base, :not_permitted) 51 errors.add(:base, :destroy_while_attached)
61 raise ActiveRecord::RecordInvalid.new(self) 52 raise ActiveRecord::RecordInvalid.new(self)
62 end 53 end
63 54
64 ActiveRecord::Base.transaction do 55 ActiveRecord::Base.transaction do
65 affected = attached_nodes.to_a 56 NodeAction.record!(:participants => [self], :user => user,
66 headline_losses = affected.select do |node| 57 :action => "asset_destroy",
67 [node.head, node.draft, node.autosave].compact.any? do |row| 58 :asset_name => name,
68 row.related_assets.exists?(:asset_id => id, :headline => true) 59 :content_type => upload_content_type,
69 end 60 :path => upload.url.sub(/\?\d+$/, ""))
70 end
71
72 metadata = {
73 :asset_name => name,
74 :content_type => upload_content_type,
75 :path => upload.url.sub(/\?\d+$/, ""),
76 }
77 metadata[:detached_from] = affected.map(&:unique_name) if affected.any?
78 metadata[:headline_removed_from] = headline_losses.map(&:unique_name) if headline_losses.any?
79
80 NodeAction.record!(:participants => [self] + affected, :user => user,
81 :action => "asset_destroy", **metadata)
82 destroy! 61 destroy!
83 end 62 end
84 end 63 end