Skip to content

Commit 18efedc

Browse files
committed
feat: Improve upload checksum
Compute checksum from in-memory IO data before persisting, instead of writing first and re-reading the entire blob from the DB. Remove the now-dead ensure_integrity_of method.
1 parent ef942a6 commit 18efedc

3 files changed

Lines changed: 13 additions & 38 deletions

File tree

lib/active_storage/service/db_service.rb

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ def initialize(public: false, **)
2727

2828
def upload(key, io, checksum: nil, **)
2929
instrument :upload, key: key, checksum: checksum do
30-
file = ::ActiveStorageDB::File.create!(ref: key, data: io.read)
31-
ensure_integrity_of(key, checksum) if checksum
32-
file
30+
data = io.read
31+
if checksum
32+
digest = Digest::MD5.base64digest(data)
33+
raise ActiveStorage::IntegrityError unless digest == checksum
34+
end
35+
::ActiveStorageDB::File.create!(ref: key, data: data)
3336
end
3437
end
3538

@@ -159,16 +162,6 @@ def generate_url(key, expires_in:, filename:, content_type:, disposition:)
159162
)
160163
end
161164

162-
def ensure_integrity_of(key, checksum)
163-
record = object_for(key)
164-
raise ActiveStorage::FileNotFoundError unless record
165-
166-
return if Digest::MD5.base64digest(record.data) == checksum
167-
168-
delete(key)
169-
raise ActiveStorage::IntegrityError
170-
end
171-
172165
def retrieve_file(key)
173166
file = object_for(key)
174167
raise ActiveStorage::FileNotFoundError unless file

spec/requests/file_controller_spec.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,13 @@ def unprocessable
143143
end
144144

145145
context "when the integrity check fails" do
146-
let(:invalid_file) { create(:active_storage_db_file, data: "Some other data") }
146+
it "fails to upload and does not persist the file", :aggregate_failures do
147+
allow(blob.service).to receive(:upload).and_raise(ActiveStorage::IntegrityError)
147148

148-
before do
149-
annotated_scope = ActiveStorageDB::File.annotate("DBService#object_for")
150-
allow(ActiveStorageDB::File).to receive(:annotate).and_return(annotated_scope)
151-
allow(annotated_scope).to receive(:find_by).and_return(invalid_file)
152-
end
153-
154-
it "fails to upload" do
155149
put blob.service_url_for_direct_upload, params: data, headers: { "Content-Type" => "text/plain" }
156150

157151
expect(response).to have_http_status(unprocessable)
152+
expect(blob.service).not_to exist(blob.key)
158153
end
159154
end
160155
end

spec/service/active_storage/service/db_service_spec.rb

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@
247247
it "fails to upload the data" do
248248
expect { upload }.to raise_exception ActiveStorage::IntegrityError
249249
end
250+
251+
it "does not persist the file" do
252+
expect { upload rescue nil }.not_to change(ActiveStorageDB::File, :count) # rubocop:disable Style/RescueModifier
253+
end
250254
end
251255

252256
context "with a duplicate key" do
@@ -300,23 +304,6 @@
300304
end
301305
end
302306

303-
describe ".ensure_integrity_of" do
304-
before { upload }
305-
306-
after { service.delete(key) rescue nil } # rubocop:disable Style/RescueModifier
307-
308-
context "when the record is missing during integrity check" do
309-
it "raises FileNotFoundError" do
310-
# Simulate the record disappearing between upload and integrity check
311-
allow(service).to receive(:object_for).with(key).and_return(nil)
312-
313-
expect {
314-
service.send(:ensure_integrity_of, key, "bad_checksum")
315-
}.to raise_exception(ActiveStorage::FileNotFoundError)
316-
end
317-
end
318-
end
319-
320307
describe "service_name_for_token" do
321308
it "returns the service name when set" do
322309
service.name = "custom_db"

0 commit comments

Comments
 (0)