From 8e98bb7758f081622ab5c4eebf3730cce22ee627 Mon Sep 17 00:00:00 2001 From: Ryan Davidson Date: Sun, 6 May 2018 16:25:05 +0100 Subject: Add option to ActiveStorage::Blob to set extract_content_type_from_io This adds a boolean argument called identify to ActiveStorage::Blob methods #create_after_upload, #build_after_upload and #upload. It allows a user to bypass the automatic content_type inference from the io. --- activestorage/CHANGELOG.md | 15 +++++++++++++++ activestorage/app/models/active_storage/blob.rb | 17 ++++++++++------- activestorage/test/models/blob_test.rb | 10 ++++++++++ activestorage/test/test_helper.rb | 4 ++-- guides/source/active_storage_overview.md | 12 ++++++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index 4c12adae56..679ca0df03 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -1,3 +1,18 @@ +* Pass in `identify: false` as an argument when providing a `content_type` for + `ActiveStorage::Attached::{One,Many}#attach` to bypass automatic content + type inference. For example: + + ```ruby + @message.image.attach( + io: File.open('/path/to/file'), + filename: 'file.pdf', + content_type: 'application/pdf', + identify: false + ) + ``` + + *Ryan Davidson* + * The Google Cloud Storage service properly supports streaming downloads. It now requires version 1.11 or newer of the google-cloud-storage gem. diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 0cd4ad8128..b8b3b62f22 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -44,21 +44,23 @@ class ActiveStorage::Blob < ActiveRecord::Base end # Returns a new, unsaved blob instance after the +io+ has been uploaded to the service. - def build_after_upload(io:, filename:, content_type: nil, metadata: nil) + # When providing a content type, pass identify: false to bypass automatic content type inference. + def build_after_upload(io:, filename:, content_type: nil, metadata: nil, identify: true) new.tap do |blob| blob.filename = filename blob.content_type = content_type blob.metadata = metadata - blob.upload io + blob.upload(io, identify: identify) end end # Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built, # then the +io+ is uploaded, then the blob is saved. This is done this way to avoid uploading (which may take # time), while having an open database transaction. - def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) - build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) + # When providing a content type, pass identify: false to bypass automatic content type inference. + def create_after_upload!(io:, filename:, content_type: nil, metadata: nil, identify: true) + build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata, identify: identify).tap(&:save!) end # Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is @@ -142,13 +144,14 @@ class ActiveStorage::Blob < ActiveRecord::Base # # Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the # checksum does not match what the service receives, an exception will be raised. We also measure the size of the +io+ - # and store that in +byte_size+ on the blob record. + # and store that in +byte_size+ on the blob record. The content type is automatically extracted from the +io+ unless + # you specify a +content_type+ and pass +identify+ as false. # # Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+ # and +create_after_upload!+. - def upload(io) + def upload(io, identify: true) self.checksum = compute_checksum_in_chunks(io) - self.content_type = extract_content_type(io) + self.content_type = extract_content_type(io) if content_type.nil? || identify self.byte_size = io.size self.identified = true diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb index daa01015f7..17151117db 100644 --- a/activestorage/test/models/blob_test.rb +++ b/activestorage/test/models/blob_test.rb @@ -43,6 +43,16 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase assert_equal "text/plain", blob.content_type end + test "create after upload extracts content_type from io when no content_type given and identify: false" do + blob = create_blob content_type: nil, identify: false + assert_equal "text/plain", blob.content_type + end + + test "create after upload uses content_type when identify: false" do + blob = create_blob data: "Article,dates,analysis\n1, 2, 3", filename: "table.csv", content_type: "text/csv", identify: false + assert_equal "text/csv", blob.content_type + end + test "image?" do blob = create_file_blob filename: "racecar.jpg" assert_predicate blob, :image? diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb index 499d955a2f..573a8e0b0b 100644 --- a/activestorage/test/test_helper.rb +++ b/activestorage/test/test_helper.rb @@ -50,8 +50,8 @@ class ActiveSupport::TestCase end private - def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") - ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type + def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain", identify: true) + ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type, identify: identify end def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: nil) diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 09af899074..292928488b 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -319,6 +319,18 @@ type you provide if it can’t do that. @message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf') ``` +You can bypass the content type inference from the data by passing in +`identify: false` along with the `content_type`. + +```ruby +@message.image.attach( + io: File.open('/path/to/file'), + filename: 'file.pdf', + content_type: 'application/pdf' + identify: false +) +``` + If you don’t provide a content type and Active Storage can’t determine the file’s content type automatically, it defaults to application/octet-stream. -- cgit v1.2.3