The uploaded picture is written after the row is committed
`Image#process_upload` ran in a `before_save`: it assigned the generated file
name and copied the bytes to disk, in the same breath, before the row existed.
If the insert failed afterwards — a validation on the owning record, a
uniqueness clash, a rollback from the surrounding transaction — the file stayed
behind with nothing pointing at it. The admin's own "Orphan" scope exists to
find rows in that family; this was the other half of it, the files with no row
at all.
Split in two: `before_validation` assigns the attributes (so validations and the
generated name still see them), `after_commit` copies the bytes. A rollback now
takes the file with it, because the copy never happens.
The same model is generated into every host, so the fix goes into the
generator template as well as ours. While there: `Image#url` comes back from
Teletype Orbit, where it was added and never flowed back, and the two admin
previews use it instead of interpolating `/api/image/#{id}` by hand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@ class Image < ApplicationRecord
|
||||
|
||||
attr_accessor :file_upload
|
||||
|
||||
before_save :process_upload, if: -> { file_upload.present? }
|
||||
before_validation :assign_upload_attributes, if: -> { file_upload.present? }
|
||||
after_commit :store_upload_file, on: [ :create, :update ], if: -> { file_upload.present? }
|
||||
|
||||
def self.ransackable_attributes(auth_object = nil)
|
||||
%w[content_type created_at deleted_at filename id original_filename updated_at]
|
||||
@@ -19,14 +20,18 @@ class Image < ApplicationRecord
|
||||
File.join(self.class.upload_path, filename.to_s)
|
||||
end
|
||||
|
||||
def url = "/api/image/#{id}"
|
||||
|
||||
private
|
||||
|
||||
def process_upload
|
||||
FileUtils.mkdir_p(self.class.upload_path)
|
||||
def assign_upload_attributes
|
||||
self.original_filename = file_upload.original_filename
|
||||
self.content_type = file_upload.content_type.presence || "application/octet-stream"
|
||||
ext = File.extname(file_upload.original_filename)
|
||||
self.filename = "#{SecureRandom.uuid}#{ext}"
|
||||
self.filename = "#{SecureRandom.uuid}#{File.extname(file_upload.original_filename)}"
|
||||
end
|
||||
|
||||
def store_upload_file
|
||||
FileUtils.mkdir_p(self.class.upload_path)
|
||||
IO.copy_stream(file_upload.to_io, file_path)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user