How to parse a single excel row data in rails - ruby-on-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] }

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

rails Import csv file, but saving blank

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.

Issue related to import excel file in rails

I am following Ryan Bates railscast website as tutorial for importing an excel file into my rails application. And finally I have done it. Now I want to perform a bit more complex operations with this excel data. I am able to import the whole content of the excel file into the database table. But now I want to get each row of the excel file before inserting into database. I need to perform operations on each row of excel file. I don't know how to achieve this.
My model name is Employee and so my table name is employees.
Now my model is:-
class Employee < ActiveRecord::Base
def self.import(file)
spreadsheet= Employee.open_spreadsheet(file)
header=spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row=Hash[[header,spreadsheet.row(i)].transpose]
em=find_by_id(row["id"])||new
em.attributes=row.to_hash.slice('firstname')
em.save
end
end
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
end
Another problem is that I want to pass the content of the row to controller, but I know that in MVC architecture I should not pass model data to controller. Then tell me is there any way out?
Don't idolize MVC, all the more so importing data is not model-related thing. If you care about clean MVC - move your code to the rake task.
Is it a background task? If so, you don't need even controller.
Otherwise if you have some user actions on the fetched strings, put your code to the controller (e.g, user uploads an excel file, inputs data for the convertation etc).
You (or users) can modify the data in iterator. row is a hash with the header as the key and the value as the ceil content. E.g., if you want to remove the special characters:
def self.import(file)
spreadsheet = Employee.open_spreadsheet(file)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |data|
spreadsheet.row(data).map! do |ceil|
ceil.class == String ? ceil.gsub!(/[!#%&"]/,'') : ceil }
end
row = Hash[[header,spreadsheet.row(data)].transpose]
em = find_by_id(row["id"]) || new
em.attributes = row.to_hash.slice('firstname')
em.save
end
end

Saving my rails object from an array of values sourced form a csv

i'm currently trying to update attributes via a csv upload.
My method looks like this:
def upload_csv
CSV.parse(params[:file].read, headers: true) do |row|
foo = Foo.find_by_id(row.to_hash["id"])
row.to_hash.each do |v|
if Foo.new.has_attribute?(v[0]) && v[0] != "id"
foo.update_attributes()
end
end
end
end
When it jumps into where I want to update my attributes, i'm getting an array that looks like this:
["bar", "22"]
How can I save that value to my foo object?
Ok, so reading you're code I'm concluding that your problem is really that you have a CSV that may contain some fields that are not in your model:
def upload_csv
excluded = %w/id created_at updated_at/
CSV.new( params[:file], headers: true) do |row|
rh = row.to_hash
foo = Foo.find_by id: rh['id']
foo.update! rh.slice(*foo.attribute_names).except(*excluded)
end
end
Note that I'm assuming params[:file] is an uploaded file from a form, in which case it's an IO object, and so can be passed into CSV.new directly (no need to read it all into memory and pass it to CSV.parse).

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