Skip to content

Commit 28db8ba

Browse files
Implement ActiveStorage::Attached::{One,Many}#attach in terms of changes
1 parent d20d6c7 commit 28db8ba

File tree

7 files changed

+183
-67
lines changed

7 files changed

+183
-67
lines changed

activestorage/lib/active_storage/attached.rb

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# frozen_string_literal: true
22

3-
require "action_dispatch"
4-
require "action_dispatch/http/upload"
53
require "active_support/core_ext/module/delegation"
64

75
module ActiveStorage
@@ -18,24 +16,6 @@ def initialize(name, record, dependent:)
1816
def change
1917
record.attachment_changes[name]
2018
end
21-
22-
def create_blob_from(attachable)
23-
case attachable
24-
when ActiveStorage::Blob
25-
attachable
26-
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
27-
ActiveStorage::Blob.create_after_upload! \
28-
io: attachable.open,
29-
filename: attachable.original_filename,
30-
content_type: attachable.content_type
31-
when Hash
32-
ActiveStorage::Blob.create_after_upload!(attachable)
33-
when String
34-
ActiveStorage::Blob.find_signed(attachable)
35-
else
36-
nil
37-
end
38-
end
3919
end
4020
end
4121

activestorage/lib/active_storage/attached/changes/create_many.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ def attachments
1212
@attachments ||= subchanges.collect(&:attachment)
1313
end
1414

15+
def blobs
16+
@blobs ||= subchanges.collect(&:blob)
17+
end
18+
1519
def upload
1620
subchanges.each(&:upload)
1721
end

activestorage/lib/active_storage/attached/changes/create_one.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# frozen_string_literal: true
22

3+
require "action_dispatch"
4+
require "action_dispatch/http/upload"
5+
36
module ActiveStorage
47
class Attached::Changes::CreateOne #:nodoc:
58
attr_reader :name, :record, :attachable

activestorage/lib/active_storage/attached/many.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,26 @@ def attachments
1212
change.present? ? change.attachments : record.public_send("#{name}_attachments")
1313
end
1414

15-
# Associates one or several attachments with the current record, saving them to the database.
15+
# Returns all attached blobs.
16+
def blobs
17+
change.present? ? change.blobs : record.public_send("#{name}_blobs")
18+
end
19+
20+
# Attaches one or more +attachables+ to the record.
21+
#
22+
# If the record is persisted and unchanged, the attachments are saved to
23+
# the database immediately. Otherwise, they'll be saved to the DB when the
24+
# record is next saved.
1625
#
1726
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
1827
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
1928
# document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
2029
# document.images.attach([ first_blob, second_blob ])
2130
def attach(*attachables)
22-
attachables.flatten.collect do |attachable|
23-
if record.new_record?
24-
attachments.build(record: record, blob: create_blob_from(attachable))
25-
else
26-
attachments.create!(record: record, blob: create_blob_from(attachable))
27-
end
31+
if record.persisted? && !record.changed?
32+
record.update(name => blobs + attachables.flatten)
33+
else
34+
record.public_send("#{name}=", blobs + attachables.flatten)
2835
end
2936
end
3037

activestorage/lib/active_storage/attached/one.rb

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@ def blank?
1717
!attached?
1818
end
1919

20-
# Associates a given attachment with the current record, saving it to the database.
20+
# Attaches an +attachable+ to the record.
21+
#
22+
# If the record is persisted and unchanged, the attachment is saved to
23+
# the database immediately. Otherwise, it'll be saved to the DB when the
24+
# record is next saved.
2125
#
2226
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
2327
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
2428
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
2529
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
2630
def attach(attachable)
27-
new_blob = create_blob_from(attachable)
28-
29-
if !attached? || new_blob != blob
30-
write_attachment build_attachment(blob: new_blob)
31+
if record.persisted? && !record.changed?
32+
record.update(name => attachable)
33+
else
34+
record.public_send("#{name}=", attachable)
3135
end
3236
end
3337

@@ -68,12 +72,6 @@ def purge_later
6872
end
6973

7074
private
71-
delegate :transaction, to: :record
72-
73-
def build_attachment(blob:)
74-
Attachment.new(record: record, name: name, blob: blob)
75-
end
76-
7775
def write_attachment(attachment)
7876
record.public_send("#{name}_attachment=", attachment)
7977
end

activestorage/test/models/attached/many_test.rb

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,73 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
3939
assert_equal "video.mp4", @user.highlights.second.filename.to_s
4040
end
4141

42+
test "attaching existing blobs to an existing, changed record" do
43+
@user.name = "Tina"
44+
assert @user.changed?
45+
46+
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
47+
assert_equal "funky.jpg", @user.highlights.first.filename.to_s
48+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
49+
assert_not @user.highlights.first.persisted?
50+
assert_not @user.highlights.second.persisted?
51+
assert @user.will_save_change_to_name?
52+
53+
@user.save!
54+
assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
55+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
56+
end
57+
58+
test "attaching existing blobs from signed IDs to an existing, changed record" do
59+
@user.name = "Tina"
60+
assert @user.changed?
61+
62+
@user.highlights.attach create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id
63+
assert_equal "funky.jpg", @user.highlights.first.filename.to_s
64+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
65+
assert_not @user.highlights.first.persisted?
66+
assert_not @user.highlights.second.persisted?
67+
assert @user.will_save_change_to_name?
68+
69+
@user.save!
70+
assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
71+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
72+
end
73+
74+
test "attaching new blobs from Hashes to an existing, changed record" do
75+
@user.name = "Tina"
76+
assert @user.changed?
77+
78+
@user.highlights.attach(
79+
{ io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
80+
{ io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpeg" })
81+
82+
assert_equal "funky.jpg", @user.highlights.first.filename.to_s
83+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
84+
assert_not @user.highlights.first.persisted?
85+
assert_not @user.highlights.second.persisted?
86+
assert @user.will_save_change_to_name?
87+
88+
@user.save!
89+
assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
90+
assert_equal "town.jpg", @user.highlights.second.filename.to_s
91+
end
92+
93+
test "attaching new blobs from uploaded files to an existing, changed record" do
94+
@user.name = "Tina"
95+
assert @user.changed?
96+
97+
@user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
98+
assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
99+
assert_equal "video.mp4", @user.highlights.second.filename.to_s
100+
assert_not @user.highlights.first.persisted?
101+
assert_not @user.highlights.second.persisted?
102+
assert @user.will_save_change_to_name?
103+
104+
@user.save!
105+
assert_equal "racecar.jpg", @user.highlights.reload.first.filename.to_s
106+
assert_equal "video.mp4", @user.highlights.second.filename.to_s
107+
end
108+
42109
test "updating an existing record to attach existing blobs" do
43110
@user.update! highlights: [ create_file_blob(filename: "racecar.jpg"), create_file_blob(filename: "video.mp4") ]
44111
assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
@@ -220,7 +287,7 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
220287
end
221288
end
222289

223-
test "attaching new blob from Hashes to a new record" do
290+
test "attaching new blobs from Hashes to a new record" do
224291
User.new(name: "Jason").tap do |user|
225292
user.highlights.attach(
226293
{ io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
@@ -229,16 +296,22 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
229296
assert user.new_record?
230297
assert user.highlights.first.new_record?
231298
assert user.highlights.second.new_record?
232-
assert_not user.highlights.first.blob.new_record?
233-
assert_not user.highlights.second.blob.new_record?
299+
assert user.highlights.first.blob.new_record?
300+
assert user.highlights.second.blob.new_record?
234301
assert_equal "funky.jpg", user.highlights.first.filename.to_s
235302
assert_equal "town.jpg", user.highlights.second.filename.to_s
236-
assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
237-
assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
303+
assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
304+
assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
238305

239306
user.save!
307+
assert user.highlights.first.persisted?
308+
assert user.highlights.second.persisted?
309+
assert user.highlights.first.blob.persisted?
310+
assert user.highlights.second.blob.persisted?
240311
assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
241312
assert_equal "town.jpg", user.highlights.second.filename.to_s
313+
assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
314+
assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
242315
end
243316
end
244317

@@ -248,16 +321,22 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
248321
assert user.new_record?
249322
assert user.highlights.first.new_record?
250323
assert user.highlights.second.new_record?
251-
assert_not user.highlights.first.blob.new_record?
252-
assert_not user.highlights.second.blob.new_record?
324+
assert user.highlights.first.blob.new_record?
325+
assert user.highlights.second.blob.new_record?
253326
assert_equal "racecar.jpg", user.highlights.first.filename.to_s
254327
assert_equal "video.mp4", user.highlights.second.filename.to_s
255-
assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
256-
assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
328+
assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
329+
assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
257330

258331
user.save!
332+
assert user.highlights.first.persisted?
333+
assert user.highlights.second.persisted?
334+
assert user.highlights.first.blob.persisted?
335+
assert user.highlights.second.blob.persisted?
259336
assert_equal "racecar.jpg", user.reload.highlights.first.filename.to_s
260337
assert_equal "video.mp4", user.highlights.second.filename.to_s
338+
assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
339+
assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
261340
end
262341
end
263342

activestorage/test/models/attached/one_test.rb

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,58 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
3232
assert_equal "racecar.jpg", @user.avatar.filename.to_s
3333
end
3434

35+
test "attaching an existing blob to an existing, changed record" do
36+
@user.name = "Tina"
37+
assert @user.changed?
38+
39+
@user.avatar.attach create_blob(filename: "funky.jpg")
40+
assert_equal "funky.jpg", @user.avatar.filename.to_s
41+
assert_not @user.avatar.persisted?
42+
assert @user.will_save_change_to_name?
43+
44+
@user.save!
45+
assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
46+
end
47+
48+
test "attaching an existing blob from a signed ID to an existing, changed record" do
49+
@user.name = "Tina"
50+
assert @user.changed?
51+
52+
@user.avatar.attach create_blob(filename: "funky.jpg").signed_id
53+
assert_equal "funky.jpg", @user.avatar.filename.to_s
54+
assert_not @user.avatar.persisted?
55+
assert @user.will_save_change_to_name?
56+
57+
@user.save!
58+
assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
59+
end
60+
61+
test "attaching a new blob from a Hash to an existing, changed record" do
62+
@user.name = "Tina"
63+
assert @user.changed?
64+
65+
@user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
66+
assert_equal "town.jpg", @user.avatar.filename.to_s
67+
assert_not @user.avatar.persisted?
68+
assert @user.will_save_change_to_name?
69+
70+
@user.save!
71+
assert_equal "town.jpg", @user.reload.avatar.filename.to_s
72+
end
73+
74+
test "attaching a new blob from an uploaded file to an existing, changed record" do
75+
@user.name = "Tina"
76+
assert @user.changed?
77+
78+
@user.avatar.attach fixture_file_upload("racecar.jpg")
79+
assert_equal "racecar.jpg", @user.avatar.filename.to_s
80+
assert_not @user.avatar.persisted?
81+
assert @user.will_save_change_to_name?
82+
83+
@user.save!
84+
assert_equal "racecar.jpg", @user.reload.avatar.filename.to_s
85+
end
86+
3587
test "updating an existing record to attach an existing blob" do
3688
@user.update! avatar: create_blob(filename: "funky.jpg")
3789
assert_equal "funky.jpg", @user.avatar.filename.to_s
@@ -71,7 +123,7 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
71123
end
72124
end
73125

74-
test "successfully replacing an existing, independent attachment on an existing record" do
126+
test "replacing an existing, independent attachment on an existing record" do
75127
@user.cover_photo.attach create_blob(filename: "funky.jpg")
76128

77129
assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
@@ -81,20 +133,7 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
81133
assert_equal "town.jpg", @user.cover_photo.filename.to_s
82134
end
83135

84-
test "unsuccessfully replacing an existing attachment on an existing record" do
85-
@user.avatar.attach create_blob(filename: "funky.jpg")
86-
87-
assert_no_enqueued_jobs do
88-
assert_raises do
89-
@user.avatar.attach nil
90-
end
91-
end
92-
93-
assert_equal "funky.jpg", @user.avatar.filename.to_s
94-
assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
95-
end
96-
97-
test "replacing an existing attachment on an existing record with the same blob" do
136+
test "replacing an attached blob on an existing record with itself" do
98137
create_blob(filename: "funky.jpg").tap do |blob|
99138
@user.avatar.attach blob
100139

@@ -149,7 +188,7 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
149188
@user.avatar.attach blob
150189

151190
assert_no_enqueued_jobs do
152-
assert_no_changes -> { @user.avatar_attachment.id } do
191+
assert_no_changes -> { @user.reload.avatar_attachment.id } do
153192
@user.update! avatar: blob
154193
end
155194
end
@@ -248,12 +287,15 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
248287
user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
249288
assert user.new_record?
250289
assert user.avatar.attachment.new_record?
251-
assert_not user.avatar.blob.new_record?
290+
assert user.avatar.blob.new_record?
252291
assert_equal "town.jpg", user.avatar.filename.to_s
253-
assert ActiveStorage::Blob.service.exist?(user.avatar.key)
292+
assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
254293

255294
user.save!
295+
assert user.avatar.attachment.persisted?
296+
assert user.avatar.blob.persisted?
256297
assert_equal "town.jpg", user.reload.avatar.filename.to_s
298+
assert ActiveStorage::Blob.service.exist?(user.avatar.key)
257299
end
258300
end
259301

@@ -262,12 +304,15 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
262304
user.avatar.attach fixture_file_upload("racecar.jpg")
263305
assert user.new_record?
264306
assert user.avatar.attachment.new_record?
265-
assert_not user.avatar.blob.new_record?
307+
assert user.avatar.blob.new_record?
266308
assert_equal "racecar.jpg", user.avatar.filename.to_s
267-
assert ActiveStorage::Blob.service.exist?(user.avatar.key)
309+
assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
268310

269311
user.save!
312+
assert user.avatar.attachment.persisted?
313+
assert user.avatar.blob.persisted?
270314
assert_equal "racecar.jpg", user.reload.avatar.filename.to_s
315+
assert ActiveStorage::Blob.service.exist?(user.avatar.key)
271316
end
272317
end
273318

0 commit comments

Comments
 (0)