How come files uploaded to S3 via URL have no extension? - ruby-on-rails

I've got picture uploads working with s3 on heroku no problem.
I also have a method that enables users to upload from a web-address.
Unfortunately, it seems as though when pictures are uploaded using this method they are saved without their file extension.
So I get this sort of link for image urls...
http://s3.amazonaws.com/mysite/images/23/original.?1311799466
If the same image had been uploaded locally it would look like this:
http://s3.amazonaws.com/mysite/images/23/original.JPG?1311799466
In my pic model this is the code I'm using to allow uploads via web addresses:
def download_remote_image
begin
self.image = open(URI.parse(self.pic_url))
rescue
errors.add_to_base("- something is wrong with the image url.")
return false
else
return true
end
end
Any ideas?

Change with this:
def download_remote_image
begin
io = open(URI.parse(pic_url))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue
end
end
Careful, recent versions of Paperclip throw errors when encountering a io object instead of File, not sure if they fixed that.

since you are using paperclip the picture url should be generated with paperclip such as:
self.image(:thumb)

Related

Mime type not getting copied or set while using remote_asset_url to upload files in carrierwave rails

I am using remote_asset_url to upload the files from temp s3 to permanent s3.
the content type in temporary s3 is correct but when i copy the file to permanent s3 the content type is set to octet-stream which leads to downloading the files/pdf instead of browsing inline. Is there a way I can sett the content type while using the remote_asset_url? My code is like following:
begin
self.remote_asset_url = self.absolute_url
self.save!
rescue CarrierWave::DownloadError => ex
if ex.message.include?("Invalid Location URI")
# "http://www.hiretale.com/files/resize_logo/13126logo original.png"
# is not working without doing this due to redirect URL not encoded properly
page = Mechanize.new.head(self.absolute_url)
self.remote_asset_url = page.uri.to_s
self.save!
elsif self.parent.is_a?(::UserProfile)
self.destroy
else
raise ex
end
end

CarrierWave customize original filename

I am attempting to customize the original filename for an image uploaded through CarrierWave by appending some sort of unique UUID. The problem is that I do not want this UUID to also be appended onto the subsequent version names.
The objective here is I want the original file to be secured by obscurity. So a user cannot just take "thumb_image.jpg" and access the original "image.jpg" simply by removing "thumb_". Something like this would be the goal:
"00000001-image.jpg" (original)
"thumb-image.jpg" (version 1)
"small-thumb-image.jpg" (version 2)
etc.
I cannot seem to find a way to get this integrated quite right using the following wiki docs:
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Create-random-and-unique-filenames-for-all-versioned-files
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Customize-your-version-file-names
Any help would be appreciated. Thanks!
Ok, it seems i was able to get this working with the following approach:
def filename
"#{secure_token}_#{original_filename.chomp(File.extname(super))}.#{file.extension}" if original_filename.present?
end
version :thumb do
process convert: 'jpg'
def full_filename(for_file = file)
"#{model.id}_thumb.jpg"
end
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end

How to specify a prefix when uploading to S3 using activestorage's direct upload?

With a standard S3 configuration:
AWS_ACCESS_KEY_ID: [AWS ID]
AWS_BUCKET: [bucket name]
AWS_REGION: [region]
AWS_SECRET_ACCESS_KEY: [secret]
I can upload a file to S3 (using direct upload) with this Rails 5.2 code (only relevant code shown):
form.file_field :my_asset, direct_upload: true
This will effectively put my asset in the root of my S3 bucket, upon submitting the form.
How can I specify a prefix (e.g. "development/", so that I can mimic a folder on S3)?
2022 update: as of Rails 6.1 (check this commit), this is actually supported:
user.avatar.attach(key: "avatars/#{user.id}.jpg", io: io, content_type: "image/jpeg", filename: "avatar.jpg")
My current workaround (at least until ActiveStorage introduces the option to pass a path for the has_one_attached and has_many_attached macros) on S3 is to implement the move_to method.
So I'm letting ActiveStorage save the image to S3 as it normally does right now (at the top of the bucket), then moving the file into a folder structure.
The move_to method basically copies the file into the folder structure you pass then deletes the file that was put at the root of the bucket. This way your file ends up where you want it.
So for instance if we were storing driver details: name and drivers_license, save them as you're already doing it so that it's at the top of the bucket.
Then implement the following (I put mine in a helper):
module DriversHelper
def restructure_attachment(driver_object, new_structure)
old_key = driver_object.image.key
begin
# Passing S3 Configs
config = YAML.load_file(Rails.root.join('config', 'storage.yml'))
s3 = Aws::S3::Resource.new(region: config['amazon']['region'],
credentials: Aws::Credentials.new(config['amazon']['access_key_id'], config['amazon']['secret_access_key']))
# Fetching the licence's Aws::S3::Object
old_obj = s3.bucket(config['amazon']['bucket']).object(old_key)
# Moving the license into the new folder structure
old_obj.move_to(bucket: config['amazon']['bucket'], key: "#{new_structure}")
update_blob_key(driver_object, new_structure)
rescue => ex
driver_helper_logger.error("Error restructuring license belonging to driver with id #{driver_object.id}: #{ex.full_message}")
end
end
private
# The new structure becomes the new ActiveStorage Blob key
def update_blob_key(driver_object, new_key)
blob = driver_object.image_attachment.blob
begin
blob.key = new_key
blob.save!
rescue => ex
driver_helper_logger.error("Error reassigning the new key to the blob object of the driver with id #{driver_object.id}: #{ex.full_message}")
end
end
def driver_helper_logger
#driver_helper_logger ||= Logger.new("#{Rails.root}/log/driver_helper.log")
end
end
It's important to update the blob key so that references to the key don't return errors.
If the key is not updated any function attempting to reference the image will look for it in it's former location (at the top of the bucket) rather than in it's new location.
I'm calling this function from my controller as soon as the file is saved (that is, in the create action) so that it looks seamless even though it isn't.
While this may not be the best way, it works for now.
FYI: Based on the example you gave, the new_structure variable would be new_structure = "development/#{driver_object.image.key}".
I hope this helps! :)
Thank you, Sonia, for your answer.
I tried your solution and it works great, but I encountered problems with overwriting attachments. I often got IntegrityError while doing it. I think, that this and checksum handling may be the reason why the Rails core team don't want to add passing pathname feature. It would require changing the entire logic of the upload method.
ActiveStorage::Attached#create_from_blob method, could also accepts an ActiveStorage::Blob object. So I tried a different approach:
Create a Blob manually with a key that represents desired file structure and uploaded attachment.
Attach created Blob with the ActiveStorage method.
In my usage, the solution was something like that:
def attach file # method for attaching in the model
blob_key = destination_pathname(file)
blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)
unless blob
blob = ActiveStorage::Blob.new.tap do |blob|
blob.filename = blob_key.basename.to_s
blob.key = blob_key
blob.upload file
blob.save!
end
end
# Attach method from ActiveStorage
self.file.attach blob
end
Thanks to passing a full pathname to Blob's key I received desired file structure on a server.
Sorry, that’s not currently possible. I’d suggest creating a bucket for Active Storage to use exclusively.
The above solution will still give IntegrityError, need to use File.open(file). Thank Though for idea.
class History < ApplicationRecord
has_one_attached :gs_history_file
def attach(file) # method for attaching in the model
blob_key = destination_pathname(file)
blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)
unless blob
blob = ActiveStorage::Blob.new.tap do |blob|
blob.filename = blob_key.to_s
blob.key = blob_key
#blob.byte_size = 123123
#blob.checksum = Time.new.strftime("%Y%m%d-") + Faker::Alphanumeric.alpha(6)
blob.upload File.open(file)
blob.save!
end
end
# Attach method from ActiveStorage
self.gs_history_file.attach blob
end
def destination_pathname(file)
"testing/filename-#{Time.now}.xlsx"
end
end

after_create file saving callback resulting in intermittent error

In my user model, I have an after_create callback that looks like this:
def set_default_profile_image
file = Tempfile.new([self.initials, ".jpg"])
file.binmode
file.write(Avatarly.generate_avatar(self.full_name, format: "jpg", size: 300))
begin
self.profile_image = File.open(file.path)
ensure
file.close
file.unlink
end
self.save
end
(self.initials is simply a utility method that returns the user's initials, so that e.g. my profile image would be "HB.jpg".)
If I call the method directly on an existing user, it works maybe 80% of the time. The other times, it gives me an error message so long I can't reproduce it here (I can't even scroll back far enough in tmux to see the start of it). The error message (or what I can see of it, anyway) comprises a list of MIME types, followed by this bit:
content type discovered from file command: application/x-empty. See documentation to allow this combination.
If I create a new user, the callback results in the same error message 100% of the time.
My method uses the Avatarly gem to generate placeholder avatars; the gem yields them in blob form, hence the creation of a Tempfile to write to.
I can't understand why the above error would occur.
Make sure that full_name has a valid return value and try moving your save call into the begin section. You may be racing against a save and the tempfile being removed/unlink.
What do you expect happens when you do this?
self.profile_image = File.open(file.path)
Without a block, this is the same as:
self.profile_image = File.new(file.path)
They both return a file object. Is profile_image in the database? I'm pretty sure it is going to be mad that you sent a File object to be persisted. If you want the data from that file in the database, do something like:
self.profile_image = File.open(file.path).read
If you want to save the tempfile's path:
self.profile_image = File.path(file.path)
If you are using the path remember that you are saving a tempfile, and the file will not last very long!
I found the solution in an issue on Paperclip's github. I don't really understand the causes very well, but it seems that this is a filesystem issue, where the Tempfile is not yet persisted to disk by the time it gets read into the model.
The solution is to do absolutely anything to the Tempfile before assigning it; file.read works just fine.
def set_default_profile_image
file = Tempfile.new([self.initials, ".jpg"])
file.binmode
file.write(Avatarly.generate_avatar(self.full_name, format: "jpg", size: 300))
file.read # <-- this fixes the issue
begin
self.profile_image = File.open(file.path)
ensure
file.close
file.unlink
end
self.save
end

Carrierwave no such file or directory

I am trying to write a method that uses the EXIFR gem to retrieve data (in this case camera model) about photos as they are uploaded using Carrierwave. The current method I am using looks like this in my model:
before_save :get_exif_data
def get_exif_data
imgfile = EXIFR::JPEG.new(photo.filename)
return
self.model = imgfile.model
end
However, I get an error "No such file or directory - IMG_0953.JPG" (or another filename).
My goal is to extract the "type of camera" data using the EXIFR gem's .model method before saving. From the EXIFR gem documentation:
EXIFR::JPEG.new('IMG_6841.JPG').model # => "Canon PowerShot G3"
The error suggests to me that my photo.filename has not yet been created. How might I process the image before it is saved?
Actually, something like the following ended up working for me:
before_save :get_camera
private
def get_camera
self.model = EXIFR::JPEG.new(photo.file.path).model
end
end
Try
EXIFR::JPEG.new(Rails.root.to_s + photo.filename_url)
carrierwave return an url look like /uploads/...
its a relative path to webapp, but EXIFR::JPEG will think it a path of system file

Resources