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.
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'm trying to build an app in Rails which will take audio file uploads and read metadata off them to populate a database. I'm using the Taglib-ruby gem to handle various file types. The uploads seem to be working on their own, but Taglib considers any file given to it as null.
Here's my controller:
class UploadsController < ApplicationController
require 'taglib'
def new
end
def create
file = params[:upload]
TagLib::FileRef.open(file) do |fileref|
unless fileref.null?
tag = fileref.tag
# properties = fileref.audio_properties
#song = Song.new(title: tag.title, artist: tag.artist, album: tag.album,
year: tag.year, track: tag.track, genre: tag.genre)
if #song.save
redirect_to songs_path
else
render 'new'
end
else
raise "file was null"
end
end
end
end
and my view for form submission:
<h1> Upload </h1>
<%= form_tag(url: { action: :create }, html: { multipart: true }) do %>
<%= label_tag :upload, "Scan your song:" %>
<%= file_field_tag :upload, multiple: true %>
<br />
<%= submit_tag "Submit" %>
<% end %>
Taglib itself seems to be working - adding "require 'taglib'" removed the error I had been getting in regards to that, and a mock-up I made of this outside of rails worked fine (so the files I'm using are also not the problem). Every time I run this, the control flow hits my raise command, and no record is saved. It's clear that fileref.null? is returning true, which suggests to me that there's something wrong with the upload process... but I'm not sure what.
Ideally, I'd like to use the multiple uploads option and run this process on each file sequentially, but I can't even get a single upload to register as anything but null.
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 am trying to upload a file to Box.com using its API REST call and the httmultiparty gem. The code is working and uploads to Box.com but does that after writing the uploaded file to the server file system as in f.write(data.read) then capturing the file path for the written file as the input parameter to the Box.com API REST call as in :filename => File.new(path). The app will be running on Heroku, so we can't save any files (read only) on Heroku's server so I would like to directly upload the file to Box.com while bypassing the writing of the file on the server but can't figure that out given that the Box.com REST call requires an object of type "File". Any help is appreciated. Thanks.
The model and view code is:
###
#The Model
###
class BoxUploader
require 'httmultiparty'
include HTTMultiParty
#base_uri 'https://api.box.com/2.0'
end
class File < ActiveRecord::Base
attr_accessible :file
attr_accessor :boxResponse
FILE_STORE = File.join Rails.root, 'public', 'files'
API_KEY = #myBoxApiKey
AUTH_TOKEN = #myBoxAuthToken
def file=(data) #uploaded file
filename = data.original_filename
path = File.join FILE_STORE, filename
#### would like to bypass the file writing step
File.open(path, "wb") do |f|
f.write(data.read)
end
#############
File.open(path, "wb") do |f|
boxResponse = BoxUploader.post('https://api.box.com/2.0/files/content',
:headers => { 'authorization' => 'BoxAuth api_key={API_KEY&auth_token=AUTH_TOKEN' },
:body => { :folder_id => '911', :filename => File.new(path)}
)
end
end
###
# The View
###
<!-- Invoke the Controller's "create" action -->
<h1>File Upload</h1>
<%= form_for #file, :html => {:multipart=>true} do |f| %>
<p>
<%= f.label :file %>
<%= f.file_field :file %>
</p>
<p>
<%= f.submit 'Create' %>
<% end %>
To upload a file from memory with HTTMultiParty, you need to supply it with an UploadIO object in place of the File object you'd normally give it. The UploadIO object can be populated using StringIO. It seems HTTMultiParty handles UploadIO objects in a special way, so you can't use the StringIO directly:
class Uploader
include HTTMultiParty
base_uri "http://foo.com"
end
string_io = StringIO.new('some stuff that pretends to be in a file')
upload_io = UploadIO.new(string_io, 'text/plain', 'bar.txt')
Uploader.post("/some/path", query: {file: upload_io})
You are aiming at a non-common use pattern, so your best shot could be to extend the existant gem, to provide the functionality you need.
There is a gem ruby-box to use with Box service at the 2.0 version of their API.
The gem is well supported and pretty easy to use.
You'll need to dig on the source code and create a new upload method.