Rails - Upload and parse multiple CSV files - ruby-on-rails

Im trying to upload and parse multiple CSV files. What I have so far:
My View
<h3>Upload multiple files</h3>
<%= form_tag({:controller => "multi_uploader", :action => "import"}, :multipart => true) do %>
<em>Upload a tab-separated .txt file to generate new rating sets.</em> <hr/>
<%= file_field_tag :file_1 %>
<hr/>
<%= file_field_tag :file_2 %>
<hr/>
<%= file_field_tag :file_3 %>
<hr/>
<%= file_field_tag :file_4 %>
<hr/>
<%= file_field_tag :file_5 %>
<hr/>
<%= file_field_tag :file_6 %>
<hr/>
<%= submit_tag "Import Data", :class => "btn btn-link"%>
<% end %>
My Controller:
def import
unless params[:file_1].nil?
file_1 = params[:file_1]
RatingSet.multi_uploader(file_1)
end
unless params[:file_2].nil?
file_2 = params[:file_2]
RatingSet.multi_uploader(file_2)
end
unless params[:file_3].nil?
file_3 = params[:file_3]
RatingSet.multi_uploader(file_3)
end
unless params[:file_4].nil?
file_4 = params[:file_4]
RatingSet.multi_uploader(file_4)
end
unless params[:file_5].nil?
file_5 = params[:file_5]
RatingSet.multi_uploader(file_5)
end
unless params[:file_6].nil?
file_6 = params[:file_6]
RatingSet.multi_uploader(file_6)
end
redirect_to "/multi_uploader", :flash => { :notice => "Successfully Uploaded." }
end
My Model method to import file:
def self.multi_uploader(file)
upload = File.open(file.path)
#Parse file and save data to db.
end
In my multi_uploader method, I parse out a key (from the file) for which category the file is supposed to be uploaded to. Everything works as expected when I upload a single file, however if I upload multiple files, the contents of all files are being saved for to a single category, rather than multiple categories. Its as if all files are being treated as a single file, rather than n individual files. What can I change to upload each file individually?

Well, as far as I see. This behavior shouldn't actually be happening. I would suggest another way of doing this code, since it's looking a bit polluted.
def import
(1..x).each do |i| #with x being the max number of files uploaded at the same time
RatingSet.multi_uploader(params["file_#{i}".to_sym])
end
redirect_to "/multi_uploader", :flash => { :notice => "Successfully Uploaded." }
end
Either way, this shouldn't solve your problem. I can't tell why this is happening to be honest.
UPDATE:
Have you considered using: <%= f.file_field :file, :multiple => true %> instead of mutiple file_tags?

Related

Generate form for with csv format

I am trying to use form for to specify two date fields, which specifies a timespan for data that I want to put inside a CSV file. I am able to create my csv file but I cant get rails to respond to csv format.
Here I have added my format: :csv in the form_for field, but it wont respond to that format in my controller.
<%= form_for(:estimation, html: {class: "estimations-form-horizontal"}, url: research_path, format: :csv, method: :get) do |f| %>
<div class="form-group-estimations">
<%= f.label :from_start_date, "Från och med" %>
<%= f.date_field :from_start_date, class: "form-control" %>
</div>
<div class="form-group-estimations">
<%= f.label :to_end_date, "Till och med" %>
<%= f.date_field :to_end_date, class: "form-control" %>
</div>
<%= f.submit "Hämta data", class: "btn btn-primary" %>
<% end %>
My controller, using format.csv ...
def research
if (params[:estimation] && params[:estimation][:from_start_date] && params[:estimation][:to_end_date])
start_date = params[:estimation][:from_start_date].to_date.beginning_of_day
end_date = params[:estimation][:to_end_date].to_date.end_of_day
if(start_date > end_date)
puts "Dates are in wrong order"
else
#estimations = Estimation.where(:created_at => start_date..end_date)
puts to_csv(#estimations)
respond_to do |format|
format.csv { send_data to_csv(#estimations), filename: "skattningar-#{Date.today}.csv" }
end
end
else
puts "Not enough data"
end
end
:url conflicts with :format If you are passing both :url and :format,
url overwrites the use of format, so you’ll need to pass it in the url
like so:
form_for user, :url => user_path(#user, :format => :json)
Source

Assigning a value to all imported data from CSV in Rails 5

I am relatively new to rails development and would appreciate some guidance.
I have an application where I am importing data from a CSV file. My goal is to assign a value to the imported CSV (Move ID) to group all the imported data.
For example (what I currently have working). I have a group of employees needing to move to a new location, I have a table called Move which has_many employee_moves (another table). I have a collection_select where I can pick from Move_IDs that exist. I can also import a CSV file where I am assigning each row to my existing Employee_Move table.
I would like, if possible, to be able to select a Move_ID, select my CSV file to import and associate each record from the CSV file to my selected Move_ID. Essentially assigning that CSV of employees moving to a specific move.
I am able to assign these values manually one at a time once imported in a edit view using a partial I have created.
<%= render 'form' %>
I hope that this is all making sense...
I have been trying to solve this for a while now so please excuse (and feel free to point out) any redundant code that may be remaining from my previous attempts.
EmployeeMove Model
require 'csv'
attr_accessor :move
belongs_to :move, optional: true
belongs_to :location, optional: true
def self.import(file)
CSV.foreach(file.path, headers: false) do |row|
EmployeeMove.create!({
:person => row[1],
:from_building => row[2],
:from_floor => row[3],
:from_room => row[4],
:to_building => row[5],
:to_floor => row[6],
:to_room => row[7],
:notes => row[8],
:move_id => move.id <-- I know this is my problem.
})
end
end
EmployeeMovesController
class EmployeeMovesController < ApplicationController
def index
#employee_moves = EmployeeMove.all
end
def new
#employee_move = EmployeeMove.new
end
def create
#employee_move = EmployeeMove.new(employee_move_params)
if #employee_move.save
flash[:notice] = "Employee Move successfully added"
redirect_to employee_move_path(#employee_move)
else
render 'new'
end
end
def show
#employee_move = EmployeeMove.find(params[:id])
end
def edit
#employee_move = EmployeeMove.find(params[:id])
end
def update
#employee_move = EmployeeMove.find(params[:id])
if #employee_move.update(employee_move_params)
flash[:notice] = "Emplyee Move successfully updated"
redirect_to employee_move_path(#employee_move)
else
render 'edit'
end
end
def import
EmployeeMove.import(params[:file])
redirect_to employee_moves_path, notice: "CSV Imported."
end
private
def employee_move_params
params.require(:employee_move).permit(:id, :person, :from_building, :from_floor, :from_room, :to_building, :to_floor, :to_room, :comments, :notes,
:Complete, :move_id, :location_id)
end
end
My Collection_Select
Currently located in the new and edit pages of Employee Move, also using a _form partial.
<%= form_for #employee_move do |f| %>
<p>
<%= f.label :Move_ID %>&nbsp
<%= collection_select(:employee_move, :move_id, Move.all, :id, :id, { :prompt => true }, { :multiple => false} ) %>
</p>
<p>
<%= f.label :employee_move_name %>&nbsp
<%= f.text_field :person %>
</p>
<p>
<%= f.label :From_Building %>&nbsp
<%= f.text_field :from_building %>
</p>
<p>
<%= f.label :From_Floor %>&nbsp
<%= f.text_field :from_floor %>
</p>
<p>
<%= f.label :From_Room %>&nbsp
<%= f.text_field :from_room %>
</p>
<p>
<%= f.label :To_Building %>&nbsp
<%= f.text_field :to_building %>
</p>
<p>
<%= f.label :To_Floor %>&nbsp
<%= f.text_field :to_floor %>
</p>
<p>
<%= f.label :To_Room %>&nbsp
<%= f.text_field :to_room %>
</p>
<p>
<%= f.label :Comment %>&nbsp
<%= f.text_field :comments %>
</p>
<p>
<%= f.label :Note %>&nbsp
<%= f.text_field :notes %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
A thought while writing this, I would prefer for my CSV import to be located in my Move show page (not meaning Employee Move show page). I currently have a Move index page where I can select from a list of created moves. Selecting a move from this list I can see the move details followed by a table populated with every Employee part of that move. This is currently working, but like I stated before, only if I manually assign the Move_ID to an employee after the CSV file has been imported.
=======================================================
Update
Thanks Simple Lime for the help thus far. The following are the suggested changes. As I mention in my comment below, I get a "undefined method 'import' for # <Move:0x00000004388a20>" currently. These are the changes I have made since my original post.
Import Method
I have moved my import method from my EmployeeMoveController to my MoveController.
def import
#move = Move.find(params[:move_id])
#move.import(params[:file])
redirect_to moves_path(#move), notice: "CSV Imported."
end
Rake Routes
import_moves POST /moves/import(.:format) moves#import
move_import GET /moves/:move_id/import(.:format) moves#import
I manually added the second route "move_import", but since the original (import_moves) also points to the same Controller#Action, maybe there is some form of conflict happening here? I'm not sure.
The following code in my Move show page correctly directs me to the correct URL, /moves/1/import.
<%= link_to "Add Employees to Move", move_import_path(#move.id) %>
Employee Move Model
I have removed the :move_id => move.id.
class EmployeeMove < ActiveRecord::Base
require 'csv'
attr_accessor :move
belongs_to :move, optional: true
belongs_to :location, optional: true
def self.import(file)
CSV.foreach(file.path, headers: false) do |row|
self.employee_moves.create!({
:person => row[1],
:from_building => row[2],
:from_floor => row[3],
:from_room => row[4],
:to_building => row[5],
:to_floor => row[6],
:to_room => row[7],
:notes => row[8]
})
end
end
end
Import View (html)
My import.html.erb file is located in /views/employee_moves.
<div class="col-md-6">
<div class="panel panel-default ">
<div class="panel-body">
<h1>move</h1>
<h1>Move</h1>
<p>
Move ID:
<%= #move.id if #move%>
</p>
<%= form_tag import_employee_moves_path, multipart: true do %>
<%= file_field_tag :file %>
<%= submit_tag "Import" %>
<% end %>
<%= link_to "All Moves", moves_path %>
</div>
</div>
</div>
I hope that this update explains well enough the current state of my problem. Please let me know if anymore information would be required.
Again! Thank you for all the help and patience with my learning.
============================================================
Final Changes that got this working me for me
I moved my self.import from my Employee_Move model to my Move model and removed self, making it an instance method.
I changed the get 'import' in my routes.rb to post 'import'
Hope this is of help to anyone with the problem I've had. Thanks again to Simple Lime for all the help!
So, assuming I understand everything correctly, you have a 2 models Move and EmployeeMove. You want to be able to upload a CSV from the Move show page and parse that CSV, creating an EmployeeMove record for each row in the CSV and the Move whose page you are currently on.
To do this, I would first move EmployeeMove.import into Move#import (so, an instance method on the Move model instead of a class method on EmployeeMove). Then, since you mention Move has_many employee_moves, instead of EmployeeMove.create!({...}), you can do
self.employee_moves.create!({...})
and not need the :move_id => move.id line in the create anymore
Lastly, move the import method from the EmployeeMovesController into MovesController and change the route in config/routes.rb to look more like /moves/:id/import (the only important part here is that it goes to the MovesController and has an :id for which move it's importing to. Then your import method on the controller should look something like
def import
#move = Move.find(params[:id])
#move.import(params[:file]) # been a while since I've done a file upload, but I assume params[:file] is working here
redirect_to moves_path(#move) # or wherever you'd like to send them
end
and that should do it for ya. Let me know if I misunderstood your problem or you want something clarified further.

Uploading model attributes with file using AWS SDK Rails 4

What I'm looking to do is save my Song model attributes in the same form that I use to upload my mp3 files. I'm currently using the AWS-SDK gem.
As of now, I can successfully upload mp3 files to AWS. However, when I attempt to save the "song_title" and "album_id" attributes as well, the data does not save, but the file is uploaded.
Song attributes:
"id", "url", "name", "created_at", "updated_at", "album_id", "song_title"
songs_controller:
class Albums::SongsController < ApplicationController
before_filter :set_user_friendships
def new
#song = Song.new
end
def create
# Make an object in your bucket for your song
obj = S3_BUCKET.objects[params[:file].original_filename]
# Upload the file
obj.write(
file: params[:file],
acl: :public_read
)
# Create an object for the song
#song = Song.new(
url: obj.public_url,
name: obj.key
)
# Save the upload
if #song.save
redirect_to songs_path, success: 'File successfully uploaded'
else
flash.now[:notice] = 'There was an error'
render :new
end
end
def index
#user_friendships = current_user.user_friendships.all
#songs = Song.all
end
def song_params
params.require(:song).permit(:id, :url, :name, :song_title, :album_id)
end
def set_user_friendships
#user_friendships = current_user.user_friendships.all #this is here because of partial UGHHH
end
end
songs/_upload_song.html.erb:
<p>Upload a song</p>
<%= form_tag songs_path, enctype: 'multipart/form-data' do %>
<%= file_field_tag :file %>
<%= submit_tag 'Upload song' %>
<% end %>
I tried changing the create method within the songs_controller to the following. (I added "song_params"):
def create
# Make an object in your bucket for your song
obj = S3_BUCKET.objects[params[:file].original_filename]
# Upload the file
obj.write(
file: params[:file],
acl: :public_read
)
# Create an object for the song
#song = Song.new(song_params,
url: obj.public_url,
name: obj.key
)
# Save the upload
if #song.save
redirect_to songs_path, success: 'File successfully uploaded'
else
flash.now[:notice] = 'There was an error'
render :new
end
end
and the songs upload form to the following:
<p>Upload a song</p>
<%= form_tag songs_path, enctype: 'multipart/form-data' do %>
<%= file_field_tag :file %>
<%= text_field_tag :song_title %>
<%= submit_tag 'Upload song' %>
<% end %>
However, I get the following error:
param is missing or the value is empty: song
I suspect that the issue has something to do with the "form_tag" vs the "form_for" difference in forms.
After taking a look around, I found that the form_tag creates a basic form, and form_for creates a form for a model object.
So when changing the upload form to the one below:
<%= form_for Song.new, enctype: 'multipart/form-data' do |f| %>
<%= f.label :file %>
<%= f.file_field :file %>
<%= f.label :song_title %>
<%= f.text_field :song_title %>
<%= f.label :album_id %>
<%= f.number_field :album_id %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
I get the following error:
undefined method `original_filename' for nil:NilClass
And the error references the following lines within the "create" method of my songs_controller:
obj = S3_BUCKET.objects[params[:file].original_filename]
I looked through the AWS-SDK documentation, but I find this information surprisingly hard to find.
Would someone mind shedding some light on how I would go about saving my Song attributes as well as uploading my Song mp3 file?
You created the method song_params, but I do not see you using it. You must explicitly set your object with the params returned by song_params in order to save the attributes upon song.save.
#song = Song.new(song_params)

Multiple text file upload Rails 3

My app is currently set up to upload and parse a single file at a time:
Form in my View:
<%= form_tag({:controller => "#{controller}", :action => "import"}, :multipart => true) do %>
<em>Upload a tab-separated .txt file.</em>
<%= file_field_tag :file %>
<br/>
<%= submit_tag "Import Data", :class => "btn btn-link"%>
<% end %>
My Controller:
def import
file = params[:file]
RatingSet.generate_uploaded_rating_set({:conditions => "data_1", :file => file})
redirect_to "/index", :flash => { :notice => "Successfully Uploaded." }
end
I then parse and add the file contents in a method in my RatingSet model.
How can I upload multiple text files to different controllers? I would like the add about 4 form upload fields, and allow the user to choose which controller they would like to upload the form to. Ideally I would like to user JS to add new form fields, for as many forms the user would like to upload, however 4 fields would suffice for now.

why is form_ tag code is not working in ruby mine 5.4.3.2.1?

I am building an ROR application in ruby mine .But the following doesn't seems to run `
<%= form_tag upload_index_path({:action => 'uploadFile'},
:multipart => true) do %>
<p><label for="upload_file">Select File</label> :
<%= file_field 'upload', 'datafile' %></p>
<%= submit_tag "Upload" %>
<% end %>`
This code is in my view file. I am trying to build a site where i can upload files. Everything appears good but when i click the upload button the file doesn't get uploaded only the url changes. Why is that??
This is my controller code
class UploadController < ApplicationController
def index
render :file => 'app\views\upload\uploadfile.html.erb'
end
def uploadFile
post = DataFile.save(params[:upload])
render :text => "File has been uploaded successfully"
end
end
This is my model code
class DataFile < ActiveRecord::Base
# attr_accessible :title, :body
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
I have not been able to find solution. I have been trying for a week.
In your form_tag declaration, remove the multipart from the path options as follows:
<%= form_tag upload_index_path({:action => 'uploadFile'}) , :multipart => true do %>
<p>
<label for="upload_file">Select File</label> :
<%= file_field 'upload', 'datafile' %>
</p>
<%= submit_tag "Upload" %>
<% end %>
http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag

Resources