rails Import csv file, but saving blank - ruby-on-rails

My github address https://github.com/ParkHyunDo/ImportCsv
I am studying how to import an excel file using roo. The import works fine, but everything is blank.
Like this....
Here is my code
product.rb
class Product < ActiveRecord::Base
acts_as_xlsx
def self.import(file)
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash.slice(accepts_nested_attributes_for)
product.save!
end
end
def self.open_spreadsheet(file)
case File.extname(file.original_filename)
# You're using a tab seperated file, so specify seperator as a tab with \t
when ".csv" then Roo::CSV.new(file.path, csv_options: {col_sep: "\t"})
when ".xls" then Roo::Excel.new(file.path)
when ".xlsx" then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
end
products_controller.rb
def import
Product.import(params[:file])
redirect_to root_url, notice: "Products imported."
end
Please help me!

This line seems strange:
product.attributes = row.to_hash.slice(accepts_nested_attributes_for)
The class method accepts_nested_attributes_for has a completely different purpose than listing the attribute names of Product. But you could use attribute_names. Try this:
product.attributes = row.to_hash.stringify_keys.slice(*attribute_names)
Note that stringify_keys might be unnecessary, depending on how exactly the hash returned by row.to_hash looks. Also note that slice takes a list of attribute names, not an array. The asterisk * allows us to use the elements of an array as individual arguments to a function.

Related

Getting -nil- values for ActiveRecord attributes in Rails database when parsing spreadsheet using roo-gem

So I am trying to build a feature within my application that will allow a User to upload a spreadsheet file filled with new products. I installed the Roo gem. I created a link for the User to download a templated Google Spreadsheet to use to upload Products to my app. For the purposes of testing this feature I am using this templated sheet to upload to my application.
The first row headers have the names of the attributes necessary to create a product in the database.
I am able to upload the file, however, no matter how many products are listed in the file it will only upload one product. Also, the product that is imported has -nil- values associated with the attributes from the file. I can verify this through the Rails Console.
For instance:
name: nil, description: nil, etc.
This is the corresponding code in my Product.rb file:
def self.import(file)
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash.slice(*accessible_attributes)
product.save!
end
end
def self.accessible_attributes
['name', 'description', 'category', 'barcode', 'quantity',
'capacity', 'threshold']
end
def self.open_spreadsheet(file)
case File.extname(file.original_filename)
when ".csv" then Roo::Csv.new(file.path, file_warning: :ignore)
when ".xls" then Roo::Excel.new(file.path, file_warning: :ignore)
when ".xlsx" then Roo::Excelx.new(file.path, file_warning: :ignore)
else raise "Unknown file type: #{file.original_filename}"
end
end
This is how I have my route setup:
resources :products do
collection { post :import }
end
Ok so the issue I had was that on the spreadsheet I was imported I had the header rows capitalized and not lowercase. Also had issues with the Capacity, Threshold and Quantity attributes because they were not apart of the Product model.
Once I edited the spreadsheet and removed those three attributes it imported correctly with no errors.
Note: one spreadsheet has multiple sheets, so what you want to do is this
spreadsheet = open_spreadsheet(file) # this return a spreadsheet
sheet1 = spreadsheet.sheet(0) # this will return the first sheet
puts spreadsheet.sheets # show all sheets

How to parse a single excel row data in rails

I am developing a ruby on rails app and I need to import an excel file for that. I am using spreadsheet gem.
Now my problem is that I can not get a single row data from excel file.
My code is here:
def actual_import(file)
puts "In the actual_import method"
spreadsheet= Employee.open_spreadsheet(file)
header=spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row=Hash[[header,spreadsheet.row(i)].transpose]
$send_name=row.to_hash.slice('firstname')
puts $send_name.to_s #this prints a weird result,,described below
puts $send_name.length
create
end
end
And my open_spreadsheet method in Employee model is:
def self.open_spreadsheet(file)
case File.extname(file.original_filename)
#when ".csv" then Roo::Csv.new (file.path nil, :ignore)
when ".xlsx" then Roo::Excelx.new (file.path)
#when ".xlsx" then Excelx.new (file.path, nil, :ignore)
else raise "Unknown file type: #{file.original_filename}"
end
end
Now $send_name prints {"firstname"=>"Abc"} and when I use $send_name.length it gives me 1 . But I need to capture only "Abc".
My excel file contains only one column named as firstname. But I need only the row data in each iteration as string.
How to solve this issue?
I recommend to use BatchFactory gem.
It uses Roo gem under the hood.
BatchFactory can read all excel file rows as array of hashes which is very handy to work with.
require 'batch_factory'
factory = BatchFactory.from_file 'filename.xlsx', keys: [:header1, :header2]
factory.rows
This will give you
[
{ header1: 'value11', header2: 'value12' },
{ header1: 'value21', header2: 'value22' },
...
]
In your case you can do
factory = BatchFactory.from_file 'filename.xlsx', keys: [:firstname]
firstnames = factory.rows.map { |row| row[:firstname] }
This will give your an array of all values from firstname column.
UPDATE
You can even omit rows in factory.rows.map because BatchFactory implement some method_missing, i.e.
firstnames = factory.map { |row| row[:firstname] }

Before Action on Import from CSV

I have a simple CSV import where the provided file is already broken (UTF characters) (German).
e.g.: The list has :
G%C3%B6tterbote
where as the right name should be
Götterbote
I'm trying to force the encoding when importing the CSV.
My Import Action
def import
Player.import(params[:file])
redirect_to players_path, notice: "Players Imported successfully"
end
My Import Method
def self.import(file)
SmarterCSV.process(file.path) do |row|
Player.create(row.first)
end
end
I found out that this converts the String successfully, but couldn't implement it successfully:
u = "G%C3%B6tterbote"
=> "G%C3%B6tterbote"
u1 = CGI::unescape(u).force_encoding('UTF-8')
=> "Götterbote"
So basically i need something like a before_action (i guess)..
You don't need a before action.
You need a pre-prossessor, well actually you need to pre-prossess yourself.
Your CSV comes with columns. Column 0, 1, 2, 3 etc (since you don't use headers).
So, for your text columns, let's call them for the sake of the example columns 1, 3, 5.
def self.import(file)
text_cols=[1,3,5] #for example
SmarterCSV.process(file.path) do |row|
text_cols.each do |column|
row[column]=CGI::unescape(row[column]).force_encoding('UTF-8')
end
Player.create(row)
end
end
Or simply, for your particular case:
def self.import(file)
SmarterCSV.process(file.path) do |row|
row.first=CGI::unescape(row.first).force_encoding('UTF-8')
Player.create(row.first)
end
end

Rails 4 how to call accessible_attributes from Model

Hi I'm trying this tuto on Rails cast (Importing csv Excel file). And I'm having an error on this line product.attributes = row.to_hash.slice(*accessible_attributes)
undefined local variable or methodaccessible_attributes' for #`
this is my model.
class Product < ActiveRecord::Base
require 'roo'
validates_presence_of :price
#attr_accessible :name, :price, :released_on #I removed this line (rails 4)
def self.to_csv(options = {})
CSV.generate(options) do |csv|
csv << column_names
all.each do |product|
csv << product.attributes.values_at(*column_names)
end
end
end
def self.import(file)
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash.slice(*accessible_attributes)
product.save!
end
end
def self.open_spreadsheet(file)
case File.extname(file.original_filename)
when ".csv" then Roo::Csv.new(file.path)
when ".xls" then Roo::Csv.new(file.path)
when ".xlsx" then Roo::Csv.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
end
end on my controller I defined the product_params
def product_params
params.require(:product).permit(:name, :price, :released_on)
end
my csv that I'm trying to import looks like this:
id,name,realased_on,price,created_at,updated_at
1,Acoustic Guitar,2012-10-03,3456.54,2013-12-09 00:00:23 UTC, 2012-12-08 23:45:46 UTC
In working on this Railscast under Rails 4.1, I got the method to work by replacing accessible_attributes with row.to_hash.keys.
def self.import(file)
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash.slice(*row.to_hash.keys)
product.save!
end
end
Actually accessible_attributes fetch that columns which are declare attr_accessible in model but in rails 4 they have removed the attr_accessible from model and used the strong_parameter instead of that so for this make an method of same name accessible_attributes in his model then inside that method declare that columns array which are you want. Such as:
def accessible_attributes
[col1_name, col2_name, ....]
end
Using the accepted solution as is didn't work out for me. I needed to add "self." to the method definition to make it work.
Another way, if the only place you are calling accessible_attributes is within self.import, is defining the fields you are looking for as a local array variable.
def self.import(file)
accessible_attributes = ['my_attribute_name1','my_attribute_name2', '...']
spreadsheet = open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
cae = find_by_id(row["id"]) || new
cae.attributes = row.to_hash.slice(*accessible_attributes)
cae.save!
end
end
To answer the previous question that why validation message shows "Price can't be blank". I think it may due to that the data is saved as General format in Excel. I encountered a similar issue and the error message went away after I changed it to text format. I don't know the reason but give it a try.

How to determine the content of the first row of a csv and excel file using in rails

I have a simple rails app where users need to upload content via csv or Excel. I don't always want to assume that the user knows all the required fields in the database, I just want users to know that first column is required and whatever content placed on the first column enters the mobile_number column in the database. My code sample of what I am talking about is below. I am using the roo gem. It's a simple app so only two database columns are used in the contacts table.
def load_imported_contacts
spreadsheet = open_spreadsheet
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
contact = Contact.find_by_id(row["id"]) || Contact.new
contact.name = "content of the second field(optional)"
contact.mobile_number = "content in the first(required and must be the first column)"
contact.save!
end
end
def open_spreadsheet
case File.extname(file.original_filename)
when ".csv" then Csv.new(file.path, nil, :ignore)
when ".xls" then Excel.new(file.path, nil, :ignore)
when ".xlsx" then Excelx.new(file.path, nil, :ignore)
else raise "Unknown file type: #{file.original_filename}"
end
end
After doing some research i finally solved my problem. I should have done this before posting this question. The solution is below
def load_imported_contacts
spreadsheet = open_spreadsheet
header = spreadsheet.row(1)
(1..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
contact = Contact.find_by_id(row["id"]) || Contact.new
contact.mobile_number = spreadsheet.cell(i,'A')
contact.name = spreadsheet.cell(i,'B')
contact
end
end

Resources