Rails upload CSV file with header - ruby-on-rails

I am using Ruby 1.9.2 with Rails 3.2.1.
I would like to create a view to upload a CSV or tab delimited file, and displays the contents of the file on the same page using a table or pagination display, then process that data in JavaScript.
How can I do this? Please walk me through any code samples you have, I am a total noob in Ruby also.

First, write a view to upload your file. You can use Paperclip for this.
Assuming you have a resource Csv, your upload form could look like this:
<%= form_for #csv, :url => csv_path, :html => { :multipart => true } do |form| %>
<%= form.file_field :attachment %>
<% end %>
Your model:
class Csv < ActiveRecord::Base
attr_accessible :attachment
has_attached_file :attachment
end
Your controller actions:
def create
#csv = Csv.create( params[:csv] )
# your save and redirect code here
end
def show
#csv = Csv.find(params[:id])
end
Having that, you can use something like this in your view:
CSV.foreach(#csv.attachment.path) do |row|
# use the row here to generate html table rows
end
Note: this is just a general idea of how this can be done and you need to have the the resource added to your routes, Paperclip gem installed and configured, etc - read the doc on how to do all that.

Just use a nice Ruby gem for parsing CSV files. This should point you in the right direction. http://fastercsv.rubyforge.org/

Related

Is there a way to have multiple file uploads turn into multiple models

So I have a model called Photo and obviously I don't want to upload one photo at a time, so I replaced the new photo form with multi-file uploading. I am not quite sure how I can make these file uploads turn into unique photo models.
<%= bootstrap_form_with(:model => photo, :local => true) do |form| %>
<%= form.file_field :image, :multiple => true, :direct_upload => true %>
<%= form.submit %>
<% end %>
Edit: I am using Active Storage on Rails 6.0.0 rc2
You can't make multiple photo models. You probally mean multiple records. A model is a blueprint of the table in your database.
Check your terminal logs when you submit the form and you will probally see that in the params you will have something like: photo => [ files here ]
So in your controller create you have to loop through the array and create a photo record for each photo, something like this:
def create
params[:photo].each do |photo|
Photo.create(file: photo.file)
end
For those that come across this problem, here was my solution:
def create
photo_params[:images].each do |image|
#photo = current_user.photos.build
#photo.image = image
#photo.save
end
end

Get path to ActiveStorage file on disk

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

Uploading a File on aWebsite using Ruby Rails 4

I am using rails 4 and I have this code.
I created a controller and called it upload (upload_controller.rb)
I put this code in it:
class UploadController < ApplicationController
def index
render :file => 'app\views\upload\uploadfile.rhtml.erb'
end
def uploadFile
post = DataFile.save(params[:upload])
render :text => "File has been uploaded successfully"
end
end
My model was called data_file.rb. The code is the following:
class DataFile < ActiveRecord::Base
attr_accessor :upload
def self.save(upload)
name = upload['datafile'].original_filename
directory = "public/data"
# create the file path
path = File.join(directory, name)
# write the file
File.open(path, "wb") { |f| f.write(upload['datafile'].read) }
end
end
My view file was called uploadFile.html.erb. My code is the following:
<h1>File Upload</h1>
<%= form_tag({:action => 'uploadFile'}, :multipart => true) do %>
<p><label for="upload_file">Select File</label>
<%= file_field 'upload', 'datafile' %></p>
<%= submit_tag "Upload" %>
<% end %>
My main goal of having this code is so that the user can upload a file to a specified location. The file type has to be anything not just images but doc, excel sheets, etc. Once I write this code and I do bundle install and then I do rake db:migrate and I go to tools and run the development. Once I do that I go to firefox and type in localhost:3000 and the page cannot show. (directs me to yahoo search or whatever).
I don't know what I am doing wrong here. Am I suppose to add a gem or change a certain line or something? I have been stuck on this issue for days now and I just want to move on to the next part of my webpage. Please help me. Thank you.

File upload to box.com without writing to file system

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.

Direct Uploads to S3 using Carrierwave

I've recently converted the below from using Paperclip to Carrierwave uploading to Amazon S3 so I can make use of the carrierwave_direct gem and then Sidekiq or another background processing gem.
class Release < ActiveRecord::Base
has_many :releases_tracks, :dependent => :destroy
has_many :tracks, :through => :releases_tracks, :order => "releases_tracks.position DESC"
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :releases_tracks
end
class Track < ActiveRecord::Base
mount_uploader :track, TrackUploader
has_many :releases_tracks, :dependent => :destroy
has_many :releases, :through => :releases_tracks
end
/views/releases/track_upload.html.erb
<%= form_for(#release, :html => { :multipart => true }) do |f| %>
<h3>Upload Tracks for <%= #release.title %></h3>
<% index = 0 %>
<%= f.fields_for :tracks do |builder| %>
<%= #release.tracks[index].name %>
<%= f.file_field :track, :class => "file styled", :title => 'Select Track'%>
<% index += 1 %>
<% end %>
<%= f.submit "Upload Tracks", :class => "submit" %>
<% end %>
Carrierwave uploads are working, but I can't figure out how to get the direct part working. Partly because I can't figure out how to incorporate the suggested form code:
<%= direct_upload_form_for #uploader do |f| %>
<%= f.file_field :track %>
<%= f.submit %>
<% end %
Or the where in my track OR release controller I place the suggested:
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
The readme https://github.com/dwilkie/carrierwave_direct and Railscast http://railscasts.com/episodes/383-uploading-to-amazon-s3 both point towards uploading your file first and then creating your database entry. In my app the db entries already exist. The Railscast does say it's possible but doesn't go through it. So that's the first problem.
The second is that I need to upload more than one file at a time. The code above does achieve that, but very slowly and it of course renders my app pretty useless as it does so.
Can anyone help? Massive thanks in advance!
First of all I would advise you not to use carrierwave_direct, I really don't like this gem, for many reasons.
One of those reasons, is that as it's said in the docs
Please be aware that this gem only supports single
file uploads. If you want to upload multiple files simultaneously
you'll have to use a javascript or flash uploader.
But if you want to use it, here is what I guess you have to do :
So first about the
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
It seems you are trying to upload tracks, and as you said your models have already been created, I guess your are trying to upload new tracks for an existing release. Correct me if I'm wrong.
so you should create the #uploader var in the #track_upload method of your ReleasesController.
class ReleasesController
...
def track_update
#uploader = User.new.track
#uploader.success_action_redirect = new_user_url
end
...
end
then in the associated view (/views/releases/track_upload.html.erb), you can use the direct_upload_form
<%= direct_upload_form_for #uploader do |f| %>
<%= f.file_field :track %>
<%= f.submit %>
<% end %>
The form will upload the file directly to s3, just after you selected the file. Then I don't know exactly how, but carrierwave_direct should give you back the url of the uploaded file.
I'm not sure about that as I've never been that far with it, but the idea is that your file just got uploaded to s3, now it has to be linked to your model, so the file doesn't get 'lost'.
So maybe carrierwave_direct is doing things on its own (I doubt that ...) by doing some ajax requests or anything else.
Anyway as you want to upload more than one file, I'd like to point you to a tutorial I recently wrote
This shows how to upload files directly to s3, without carrierwave_direct, but by doing things on your own. This requires a little bit more code and time, but you have more control about what's happening.
In your case, you'll want to put the form I'm using in my tutorial in your view, in the /views/releases/track_upload.html.erb view.
Then once you'll select a file, the successful AJAX request(emitted by the jQueryFileUpload plugin) will give you the URL of the uploaded file so you can save it in your Track model (you'll probably want to emit a new AJAX request to your server to create the new Track model, or to populate an other form on the page, like the one you were using in the /views/releases/track_upload.html.erb file, and then the tracks will be saved on submit.)
I'm not sure I'm really clear about that, let me know if you need more explanations.
And the good thing about that is that if you simply add multiple to your file input, then the awesome jQueryFileUpload plugin will send a request per file to s3, then you'll get the URL's of the uploaded files in each ajax results :D
And you can tweak things to add progress bars, and things like that with the jQuery plugin, you can really create awesome things.
Hope it'll help you !

Resources