Rails ActiveStorage Updating attached file after processing - ruby-on-rails

Fairly new to using ActiveStorage (love it so far!), but I am running into an issue with trying to figure out how to update a previously attached item.
I have a front-end form that allows a user to upload a PDF, my backend then takes the PDF and scrapes it for data, then exports the data to an excel spreadsheet. My code (when I call to re-attach the export), I see my item in my specified s3 bucket, but its a hashed key name.
class Export < ApplicationRecord
has_one_attached :item
def process_pdf!
pdf_file_name = "#{item.blob.key}_#{item.blob.filename.to_s}"
xls_file_name = "#{item.blob.key}_#{item.blob.filename.to_s.sub('pdf', '')}"
file = "#{Rails.root}/tmp/#{pdf_file_name}"
File.open(file, 'wb') do |f|
f.write(pdf.download)
end
PdfScraper.call(pdf_file: file, output_name: xls_file_name)
self.item.attach(
io: File.open("#{Rails.root}/tmp/#{xls_file_name}"),
filename: "#{xls_file_name}.xlsx",
content_type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
File.delete(file)
true
end
end
How do I attach the generated excel spreadsheet?

Related

Rails attatchment not persisting

I have an attachment
class User < ApplicationRecord
has_one_attached :avatar
when I create the user and I perform on my seed
user = User.create({"name":"pedro})
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
the avatar gets attached
However, when I try to replicate/update it on my controller:
user = User.find(params["id"])
user.avatar.purge
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
It somehow gets attatched (if I perform a user.avatar.attached? before it returns false and true after I attach it) but the blob doesn't persist/get saved into storage. It somehow only persists with newly created objects.
I've tried looking for questions with a similar issue with no success.
I don't see anywhere that you are saving it to the database. Try user.new instead of user.create. After attaching the file save it.
user = User.new({"name":"pedro})
file = open("#{Rails.root}/app/assets/img/user.png")
user.avatar.attach(io: file, filename: "user.png")
user.save
it is good practice to add the content type
user.avatar.attach(io: file, filename: "user.png" , content_type:
'image/png')
Edit (Op comment update): you can use user.save(validate: false) to skip validations

How to copy one object from one model to another model with Rails ActiveStorage

I'm currently using Rails 5.2.2.
I'm trying to copy one image between 2 models. So I want 2 files, and 2 entries in blobs and attachments.
My original object/image is on AWS S3.
My original model is photo, my target model is image.
I tried this:
image.file.attach(io: open(best_photo.full_url), filename: best_photo.filename, content_type: best_photo.content_type)
full_url is a method added in photo.rb:
include Rails.application.routes.url_helpers
def full_url
rails_blob_path(self.file, disposition: "attachment", only_path: true)
end
I got this error, as if the file was not found:
No such file or directory # rb_sysopen -
/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBHZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2f865041b01d2f2c323a20879a855f25f231289d/881dc909-88ab-43b6-8148-5adbf888b399.jpg?disposition=attachment
I tried other different things such (this method is used when displaying images with image_tag() and works correctly:
def download_variant(version)
variant = file_variant(version)
return rails_representation_url(variant, only_path: true, disposition: "attachment")
end
Same error.
I verified and the file is present on the S3 server.
What did I miss ?
Thanks.
You can attach the source blob to the target:
image.file.attach best_photo.file.blob
Updated for Rails 6+ - this worked for me
image.file.attach(io: StringIO.new(best_photo.download),
filename: best_photo.filename,
content_type: best_photo.content_type)
OK, I got it.
I used service_url:
image.file.attach(io: open(best_photo.file_variant("large").service_url), filename: best_photo.file.blob.filename, content_type: best_photo.file.blob.content_type)
It's possible to use file_blob instead of file.blob
You can copy using update
image.update(file: best_photo.file_blob)
or attach
image.file.attach(best_photo.file_blob)
Both methods actually just create new blob association, there is no physical copying of attachment. So be careful when you call image.blob.purge or if you have dependent: :purge_later
If you want real copy you need to read attachment with ActiveStorage::Blob#download
image.file.attach(
io: StringIO.new(best_photo.file.download),
filename: best_photo.file.filename,
content_type: best_photo.file.content_type
)
or with ActiveStorage::Blob#open
best_photo.file_blob.open do |tempfile|
image.file.attach(
io: tempfile,
filename: best_photo.file.filename,
content_type: best_photo.file.content_type
)
end
Be careful with large files

Active Storage with Amazon S3 not saving with filename specified but using file key instead

I am having an issue with Active Storage. When I upload to Amazon S3, instead of saving the file inside the bucket with the original name like myfile.zip it is saving it as the key which is associated with that file. So in Cyberduck I am seeing something like this: 5YE1aJQuFYyWNr6BSHxhQ48t. Without any file extension.
I am not sure if there is some setting in Rails 5 or whether it is within Amazon S3 but I have spent hours Googling around to figure out why this is happening.
Any pointers would be really appreciated!
Best regards,
Andrew
This is by design, from ActiveStorage. The file is stored by it's key and without extension on S3, but when the URL is generated by ActiveStorage, the disposition and filename are set.
def url(key, expires_in:, filename:, disposition:, content_type:)
instrument :url, key: key do |payload|
generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
response_content_disposition: content_disposition_with(type: disposition, filename: filename),
response_content_type: content_type
payload[:url] = generated_url
generated_url
end
end
This is probably done to avoid filename escaping issues that you'd run into otherwise.
You can read more about the Content-Disposition headers here.
You can still reference the name with filename accessor.
class User < ApplicationRecord
has_one_attached :photo
...
end
filename = User.first.photo.filename
In order to have a custom filename on S3, you should update both blob.key and the name on S3.
Active storage uploads images on S3 using blob.key as remote image path and name.
For my usage, I only changed the name for 'images variants' with a Monkey Patch that allows to generate a key terminating by the filename :
config/initializers/active_storate_variant.rb :
ActiveStorage::Variant.class_eval do
def key
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}/#{filename}"
end
end
So when I need the public url for an image variant, I just call image.url('400x400')
This is how my Image model is customized :
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_one_attached :picture
SIZES = { '400x400' => '400x400' }
def url(size)
return "https://placehold.it/#{size}" unless picture.attached?
'https://my_s3_subdomain.amazonaws.com/' +
picture.variant(resize: SIZES[size]).processed.key
end
...
end
If someone has a better way to do that, I would be happy to see it :)

write pdf from dropbox to paperclip

I am attempting to copy pdf files from dropbox to paperclip. Everything seems to work except that the pdf's fail to load when I try and view them from my rails application.
I have paperclip working:
class Document < ActiveRecord::Base
has_attached_file :file, content_type: ["application/pdf", "application/x-pdf"]
validates_attachment_content_type :file, content_type: ["application/pdf", "application/x-pdf", "application/rtf", 'application/x-rtf', 'text/rtf', 'text/plain']
end
To upload the file to paperclip I do the following:
contents = DropboxClient.new(DROPBOX_AUTH_TOKEN).get_file(PATH_TO_MY_PDF)
file = StringIO.new(contents)
file.class.class_eval{ attr_accessor :content_type, :original_filename }
file.content_type = "application/pdf"
file.original_filename = "my_pdf.pdf"
Document.create(file: file)
But when I click on a link that sends to a send_data(#document.file, filename: #document.file_file_name) the file is downloaded but cannot be opened. The downloaded file has content type of pdf and says it's 4kb where as the original is 10kb.
If it's helpful the contents that DropboxClient.new(DROPBOX_AUTH_TOKEN).get_file(PATH_TO_MY_PDF) returns is of the form
\x03Y\x8E\xCB\xC3\x97Zi\xA5\x92\xBB\xF1\xFD\xCD\x1D\xF5\x18\xF4\x95\xEB/\xD3\xAB\xCF\x9D\xB6\x02\x89\xDB\x9E\xE9\xBFJ!pF\xCB\xEA(\xC4B*\
but much longer.
Turns out everything was working fine. But in using send data you need to send the raw data, not the paperclip file object (duh!). Changing to
send_data(Paperclip.io_adapters.for(#document.file).read, filename: #document.file_file_name)
solved my problems.

rename and download amazon file ruby mongoid

When I click a link, the amazon file is downloading fine. I have thousands of pdf files in amazon. Now mongoid id as the name of that amazon files. When I download the the files its save with that id's. But I would like to rename the file before save that file to local. I have name field in my mongoid.
include Mongoid::Paperclip
has_mongoid_attached_file :profile_doc
when I link a like this controller action will call
def download
if #company.send(doc).exists?
redirect_to #company.send(doc).expiring_url
else
respond_with_error(:not_found)
end
end
How I rename the file when download the file. now file is downloading like this 50sfdkkmzd.pdf I would like to save name.pdf.
Thanks for your time.
try this, it should work fine
def download
if #company.send(doc).exists?
data = open(#company.send(doc).expiring_url)
send_data data.read, :filename => "name.pdf", :type => data.content_type
else
respond_with_error(:not_found)
end
end

Resources