I need to be able to attach a file to a mail (using Mailer) for a recently uploaded file which is not linked to any Model.
In the code that goes for the upload form:
<%= form_for(:mail, :url => {:action => 'send_mail'}, :html => {:multipart => true}) do |f| %>
<table summary="send_table">
<tr>
<th>Attachment</th>
<td><%= f.file_field(:attachment) %><a id="attachment"></a></td>
</tr>
</table>
<%= submit_tag "Send!" %>
Now, what I am looking into doing in the send_mail action is something like:
MyMailer.send_mail(params[:mail][:attachment]).deliver
with params[:mail][:attachment] being the path to the temp file uploaded with the form. How can one do that?
This also implies another question: Should I manually delete the file from the temp once the mail is sent? If yes, how?
Copying the answer from the comments in order to remove this question from the "Unanswered" filter:
Finally nailed it:
unless (params[:mail][:attachment]).nil?
uploader = AttachmentUploader.new
uploader.cache!(params[:mail][:attachment])
#file_name = uploader.filename
#file_data = uploader.file.read()
end
and then
MyMailer.send_mail(#file_name,#file_data)
~ answer per user1563325
I'd like to add that for the scenario described here, maybe you don't need CarrierWave for such a temporary upload situation. When uploading using file_field, Rails saves it into a Tempfile, whose path you can access like this:
params[:mail][:attachment].path
These files are deleted automatically so you shouldn't need to worry about them, as the Rails docs explain:
Uploaded files are temporary files whose lifespan is one request. When the object is finalized Ruby unlinks the file, so there is no need to clean them with a separate maintenance task.
Related
I need to get the path to the file on disk which is using ActiveStorage. The file is stored locally.
When I was using paperclip, I used the path method on the attachment which returned the full path.
Example:
user.avatar.path
While looking at the Active Storage Docs, it looked like rails_blob_path would do the trick. After looking at what it returned though, it does not provide the path to the document. Thus, it returns this error:
No such file or directory # rb_sysopen -
Background
I need the path to the document because I am using the combine_pdf gem in order to combine multiple pdfs into a single pdf.
For the paperclip implementation, I iterated through the full_paths of the selected pdf attachments and load them into the combined pdf:
attachment_paths.each {|att_path| report << CombinePDF.load(att_path)}
Use:
ActiveStorage::Blob.service.path_for(user.avatar.key)
You can do something like this on your model:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_on_disk
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
I'm not sure why all the other answers use send(:url_for, key). I'm using Rails 5.2.2 and path_for is a public method, therefore, it's way better to avoid send, or simply call path_for:
class User < ApplicationRecord
has_one_attached :avatar
def avatar_path
ActiveStorage::Blob.service.path_for(avatar.key)
end
end
Worth noting that in the view you can do things like this:
<p>
<%= image_tag url_for(#user.avatar) %>
<br>
<%= link_to 'View', polymorphic_url(#user.avatar) %>
<br>
Stored at <%= #user.image_path %>
<br>
<%= link_to 'Download', rails_blob_path(#user.avatar, disposition: :attachment) %>
<br>
<%= f.file_field :avatar %>
</p>
Thanks to the help of #muistooshort in the comments, after looking at the Active Storage Code, this works:
active_storage_disk_service = ActiveStorage::Service::DiskService.new(root: Rails.root.to_s + '/storage/')
active_storage_disk_service.send(:path_for, user.avatar.blob.key)
# => returns full path to the document stored locally on disk
This solution feels a bit hacky to me. I'd love to hear of other solutions. This does work for me though.
You can download the attachment to a local dir and then process it.
Supposing you have in your model:
has_one_attached :pdf_attachment
You can define:
def process_attachment
# Download the attached file in temp dir
pdf_attachment_path = "#{Dir.tmpdir}/#{pdf_attachment.filename}"
File.open(pdf_attachment_path, 'wb') do |file|
file.write(pdf_attachment.download)
end
# process the downloaded file
# ...
end
I'm trying to use smarter_csv to parse csv files with my Rails app. But the documentation only explains how to parse a file that already belongs to the app.
I want to parse a file that's stored locally on my computer. So I think I have to upload the file, parse it, and then delete it.
This is how far I got:
<%= form_tag({action: :upload}, multipart: true) do %>
<%= file_field :csv %>
<%= submit_tag 'Submit' %>
<% end %>
So then how can I reference and use the uploaded file in my controller action?
def upload
#save file temporarily to app
filename = #filename
#parse file with smarter_csv
#File.delete(filename)
end
To get the file path as a string you need to do the following:
filename = params[:csv].path
as params[:csv] is a UploadedFile object. You don't need handle the temp file yourself, i.e. storing and deleting it. Rails would do that for you. As per documentation:
Uploaded files are temporary files whose lifespan is one request. When the object is finalized Ruby unlinks the file, so there is no need to clean them with a separate maintenance task.
I am not using paperclip or carrierwave or any other gems for interaction with amazon web services s3. In fact, I am not using any models, just directly interacting with S3 objects.
Can someone please provide some code as to how to directly download objects(files) from AWS S3
This is my code so far :
View:
<h2>Download Files</h2>
<%#root_files.each do |file| %>
<% next if #obj %>
<td>Filename: <b><%= file %></b></td>
<td><%= link_to "Download", download_url_for(file) %></td>
<% end %>
Corresponding Controller:
def xxx
bucket = AWS::S3.new.buckets[ ENV["BUCKET"]]
#root_files = bucket.as_tree.children.select(&:leaf?).collect(&:key)
end
download_url_for method:
def download_url_for(file_key)
s3 = AWS::S3.new
bucket = s3.buckets[ ENV["BUCKET"]]
object = bucket.objects[file_key]
File.open('xxxxx', 'wb') do |file|
object.read do |chunk|
file.write(chunk)
end
end
end
I need to be able to save the downloaded file on any browsable non-pretedetermined location on my desktop.Id really appreciate view on how to modify the download_url_for method to achieve this.
=====================================
I just tried using
<td><%= link_to "Download",object.value.url_for(:read).to_s %></td>
But on downloading I only get a zip file with xml content inside it, not the original file (word document in my case) --- any thoughts on how to modify this so as to get the actual file type and contents? Thanks
=======================Heres my code for uploading the file
View code:
<%= form_tag({:action => 'create_file'}, multipart: true) do %>
<%= file_field_tag 'uploaded_file' %><br />
<%= submit_tag 'Upload' %> <br />
<% end %>
Controller action - create_file action
def create_file
s3 = AWS::S3.new
bucket = s3.buckets[ENV["BUCKET"]]
key = params[:uploaded_file].original_filename
object = bucket.objects[key]
object.write(params[:uploaded_file].read)
end
Can someone suggest as to how I can check the MIME type or preserve the filename while uploading if thats the issue as mentioned by #Frederick Cheung? thanks
You'd probably be better off providing a url for users to download directly from S3 rather than trying to stream the file through your Rails app.
The aws-sdk provides the url_for method to accomplish this: http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html#url_for-instance_method. It takes a bunch of options for expiration, security, caching, etc. A basic example:
<td><%= link_to "Download", file.url_for(:read) %></td>
--EDIT--
To access the url_for method in the view, you'll need references to the s3 objects. So in your controller, you want to collect objects from the leaf nodes, not keys:
#root_files = bucket.as_tree.children.select(&:leaf?).collect(&:object)
I want users to be able to upload an image and have it be associated with a particular model. When I create my migration, what type do I use for my column? In the view I would like it to look something like this:
<%= form_for #person, :html => {:multipart => true} do |f| %>
<%= f.file_field :picture %>
<% end %>
Thanks guys :)
Probably :binary -- but, have you considered using something like Paperclip to handle image uploads for you? It can get really complicated otherwise.
I recommend the usage of an external plugin such as Paperclip or Carrierwave.
There's also the AWS-3 gem, if you just want cloud storage. I would think performance alone would be enough to not store an image in a DB.
How do you handle file upload in rail without attaching them to active record ?
I just want to write the files to the disk.
Thanks,
If I understand correctly what you need then the most simple example would be this:
The controller:
class UploadController < ApplicationController
def new
end
def create
name = params[:upload][:file].original_filename
path = File.join("public", "images", "upload", name)
File.open(path, "wb") { |f| f.write(params[:upload][:file].read) }
flash[:notice] = "File uploaded"
redirect_to "/upload/new"
end
end
The view:
<% flash.each do |key, msg| %>
<%= content_tag :div, msg, :class => [key, " message"], :id => "notice_#{key}" %>
<% end %>
<% form_tag '/upload/create', { :multipart => true } do %>
<p>
<%= file_field_tag 'upload[file]' %>
</p>
<p>
<%= submit_tag "Upload" %>
</p>
<% end %>
This would let you upload any file without any checks or validations which in my opinion isn't that usefull.
If I would do it myself then I would use something like validatable gem or tableless gem just tableless is not supported anymore. These gems would allow you to validate what you're uploading to make it more sane.
You can just move the temporary file to destiny path using FileUtils
tmp = params[:my_file_field].tempfile
destiny_file = File.join('public', 'uploads', params[:my_file_field].original_filename)
FileUtils.move tmp.path, destiny_file
The Tempfile documentation shows an example that's equivalent to Rytis's code, which is fine most of the time. But when you call tempfile.read, Ruby is reading the whole file as a single chunk into memory, which is sub-optimal.
However, FileUtils provides a copy_stream method, and IO, at least in Ruby 2.0, provides a copy_stream implementation that handles writing directly to a filepath (FileUtils.copy_stream requires File-like objects on both sides, or so say the docs).
In my case, I was initiating a large multi-file upload via AJAX, and wanted to avoid reading the whole file(s) into Ruby's memory before writing to disk.
In the example below, params[:files] is an Array of ActionDispatch::Http::UploadedFile instances, and local_filepath is a string pointing to a non-existing file in an existing directory. For brevity, I'll assume I'm only uploading one file:
IO.copy_stream(params[:files][0].tempfile, local_filepath)
The ActionDispatch::Http::UploadedFile instance has a .tempfile field that's just a regular Tempfile instance.
I'm not actually sure that Ruby still isn't reading the whole file into memory—I didn't benchmark anything—but it's a lot more possible than it is with the localfile.write(tempfile.read) syntax.
tl;dr: IO.copy_stream(your_tempfile, your_disk_filepath) is more concise, if not faster.
You could try using the Rails plugin Attachment_fu to handle file uploads. It allows you to save uploads to the file system instead of the database.