Skip to content

Commit 8bb97ea

Browse files
committed
Allow to detach an attachment when record is not persisted
Detaching a not persisted record no longer raise an error. Moves the `detach` logic of `ActiveStorage::Attached` to `Attached::Changes API`.
1 parent 1f16768 commit 8bb97ea

File tree

8 files changed

+103
-17
lines changed

8 files changed

+103
-17
lines changed

activestorage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Allow to detach an attachment when record is not persisted
2+
3+
*Jacopo Beschi*
4+
15
* Use libvips instead of ImageMagick to analyze images when `active_storage.variant_processor = vips`
26

37
*Breno Gazzola*

activestorage/lib/active_storage/attached/changes.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ module Attached::Changes #:nodoc:
1212
autoload :DeleteOne
1313
autoload :DeleteMany
1414

15+
autoload :DetachOne
16+
autoload :DetachMany
17+
1518
autoload :PurgeOne
1619
autoload :PurgeMany
1720
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveStorage
4+
class Attached::Changes::DetachMany #:nodoc:
5+
attr_reader :name, :record, :attachments
6+
7+
def initialize(name, record, attachments)
8+
@name, @record, @attachments = name, record, attachments
9+
end
10+
11+
def detach
12+
if attachments.any?
13+
attachments.delete_all if attachments.respond_to?(:delete_all)
14+
record.attachment_changes.delete(name)
15+
end
16+
end
17+
end
18+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveStorage
4+
class Attached::Changes::DetachOne #:nodoc:
5+
attr_reader :name, :record, :attachment
6+
7+
def initialize(name, record, attachment)
8+
@name, @record, @attachment = name, record, attachment
9+
end
10+
11+
def detach
12+
if attachment.present?
13+
attachment.delete
14+
reset
15+
end
16+
end
17+
18+
private
19+
def reset
20+
record.attachment_changes.delete(name)
21+
record.public_send("#{name}_attachment=", nil)
22+
end
23+
end
24+
end

activestorage/lib/active_storage/attached/many.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ class Attached::Many < Attached
1616
# Purges each associated attachment through the queuing system.
1717
delegate :purge_later, to: :purge_many
1818

19+
##
20+
# :method: detach
21+
#
22+
# Deletes associated attachments without purging them, leaving their respective blobs in place.
23+
delegate :detach, to: :detach_many
24+
1925
delegate_missing_to :attachments
2026

2127
# Returns all the associated attachment records.
@@ -60,14 +66,13 @@ def attached?
6066
attachments.any?
6167
end
6268

63-
# Deletes associated attachments without purging them, leaving their respective blobs in place.
64-
def detach
65-
attachments.delete_all if attached?
66-
end
67-
6869
private
6970
def purge_many
7071
Attached::Changes::PurgeMany.new(name, record, attachments)
7172
end
73+
74+
def detach_many
75+
Attached::Changes::DetachMany.new(name, record, attachments)
76+
end
7277
end
7378
end

activestorage/lib/active_storage/attached/one.rb

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ class Attached::One < Attached
1616
# Purges the attachment through the queuing system.
1717
delegate :purge_later, to: :purge_one
1818

19+
##
20+
# :method: detach
21+
#
22+
# Deletes the attachment without purging it, leaving its blob in place.
23+
delegate :detach, to: :detach_one
24+
1925
delegate_missing_to :attachment, allow_nil: true
2026

2127
# Returns the associated attachment record.
@@ -67,21 +73,13 @@ def attached?
6773
attachment.present?
6874
end
6975

70-
# Deletes the attachment without purging it, leaving its blob in place.
71-
def detach
72-
if attached?
73-
attachment.delete
74-
write_attachment nil
75-
end
76-
end
77-
7876
private
79-
def write_attachment(attachment)
80-
record.public_send("#{name}_attachment=", attachment)
81-
end
82-
8377
def purge_one
8478
Attached::Changes::PurgeOne.new(name, record, attachment)
8579
end
80+
81+
def detach_one
82+
Attached::Changes::DetachOne.new(name, record, attachment)
83+
end
8684
end
8785
end

activestorage/test/models/attached/many_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,24 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
440440
end
441441
end
442442

443+
test "detaching when record is not persisted" do
444+
[ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
445+
user = User.new
446+
user.highlights.attach blobs
447+
assert user.highlights.attached?
448+
449+
perform_enqueued_jobs do
450+
user.highlights.detach
451+
end
452+
453+
assert_not user.highlights.attached?
454+
assert ActiveStorage::Blob.exists?(blobs.first.id)
455+
assert ActiveStorage::Blob.exists?(blobs.second.id)
456+
assert ActiveStorage::Blob.service.exist?(blobs.first.key)
457+
assert ActiveStorage::Blob.service.exist?(blobs.second.key)
458+
end
459+
end
460+
443461
test "purging" do
444462
[ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
445463
@user.highlights.attach blobs

activestorage/test/models/attached/one_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,22 @@ def upload.open
451451
end
452452
end
453453

454+
test "detaching when record is not persisted" do
455+
create_blob(filename: "funky.jpg").tap do |blob|
456+
user = User.new
457+
user.avatar.attach blob
458+
assert user.avatar.attached?
459+
460+
perform_enqueued_jobs do
461+
user.avatar.detach
462+
end
463+
464+
assert_not user.avatar.attached?
465+
assert ActiveStorage::Blob.exists?(blob.id)
466+
assert ActiveStorage::Blob.service.exist?(blob.key)
467+
end
468+
end
469+
454470
test "purging" do
455471
create_blob(filename: "funky.jpg").tap do |blob|
456472
@user.avatar.attach blob

0 commit comments

Comments
 (0)