Ruby undefined method `[]' for nil:NilClass (NoMethodError) error - ruby-on-rails

I'm trying to figure out why I keep getting the following error:
From the following code:
def information_transfer()
file_contents = CSV.read("test.csv", col_sep: ",", encoding: "ISO8859-1")
file_contents2 = CSV.read("applicantinfo.csv", col_sep: ",", encoding:"ISO8859-1")
arraysize = file_contents.length
arraysize1 = file_contents2.length
for i in 1..arraysize
for x in 1..arraysize1
if file_contents[i][0] == file_contents2[x][0]
CSV.open("language_output.csv", "wb") do |csv|
csv << [file_contents[i][0], file_contents[i][1], file_contents[i][2],file_contents[i][3], file_contents[i][4],
file_contents[i][5], file_contents[i][6], file_contents[i][7], file_contents[i][8],file_contents[i][9],
file_contents[i][10], file_contents[i][11], file_contents[i][12], file_contents[i][13], file_contents[i][14],
file_contents[i][15], file_contents[i][16], file_contents[i][17], file_contents[i][18], file_contents2[i][24],file_contents2[i][25],
file_contents2[i][26],file_contents2[i][27], file_contents2[i][28], file_contents2[i][29], file_contents2[i][30], file_contents2[i][31], file_contents2[i][32], file_contents2[i][33]]
end
end
end
end
end
I'm basically trying to take two individual .csv files and merge certain columns together. I have two arrays (file_contents and file_contents2) that are reading the individual csv files and storing the contents in arrays. For some reason i'm getting a syntax error for my if statement. I was hoping someone could help me figure out why the if statement that I wrote isn't valid. I figured it would be. Any help is appreciated. Thanks!

Seems like one of file_contents or file_contents2 is empty.
You can skip the loop if you don't want to raise the error on that specific line.
next if file_contents[i].blank? || file_contents2[i].blank?
if file_contents[i][0] == file_contents2[x][0]

One of your arrays file_contents or file_contents2 might be empty. Output both, as well as printing file_contents[i][0] and file_contents2[x][0] before your if statement.
You can make a simple change that should work:
for i in 0..arraysize
for x in 0..arraysize1
And add an error check:
if !file_contents[i].blank? and !file_contents2[x].blank? and file_contents[i][0] == file_contents2[x][0]

for i in 1..arraysize
for x in 1..arraysize1
Array indexes run from 0 to length − 1 in Ruby; loop in 0...arraysize instead.
If file_contents2[i] can or should be written as file_contents2[x], you can just loop over the arrays’ contents directly:
for a in file_contents
for b in file_contents2
and use slices to get consecutive array elements into another array:
def information_transfer()
file_contents = CSV.read("test.csv", col_sep: ",", encoding: "ISO8859-1")
file_contents2 = CSV.read("applicantinfo.csv", col_sep: ",", encoding: "ISO8859-1")
for a in file_contents
for b in file_contents2
if a[0] == b[0]
CSV.open("language_output.csv", "wb") do |csv|
csv << a[0..18] + b[24..33]
end
end
end
end
end
and if you’re trying to join the two files one-to-one, you can do that more efficiently by putting the key into a hash. You also probably didn’t mean to reopen the output file every time.
def information_transfer()
file_contents = CSV.read("test.csv", col_sep: ",", encoding: "ISO8859-1")
file_contents2 = CSV.read("applicantinfo.csv", col_sep: ",", encoding: "ISO8859-1")
h = Hash[file_contents.collect { |row| [row[0], row] }]
CSV.open("language_output.csv", "wb") do |csv|
for b in file_contents2
a = h[b[0]]
csv << a[0..18] + b[24..33]
end
end
end

Related

Rails CSV remove column that have empty header

I am receiving a csv file that has some blank headers but data exists in these columns. I want to remove the blank header and it's associated column in rails.
Sample csv
#report
,Code,Price,Orders,,Mark,
1,X91,4.55,4,xxx,F,23
What I'd like returned:
Code,Price,Orders,Mark
A91,4.55,4,F
This is what I have so far as there is also comments on the csv which i am ignoring.
CSV.open("output.csv", "w") do |output_csv|
CSV.foreach("out.csv", encoding: "bom|utf-8", skip_lines: /^#/, headers: true).with_index(0) do |row, i|
output_csv << row.headers if i == 0
output_csv << row
end
end
You can use CSV::Row's delete_if method https://ruby-doc.org/stdlib-2.4.1/libdoc/csv/rdoc/CSV/Row.html#method-i-delete_if, something like:
CSV.open("output.csv", "w") do |output_csv|
CSV.foreach("out.csv", encoding: "bom|utf-8", skip_lines: /^#/, headers: true) do |row|
clean_row = row.delete_if { |header, _field| header.blank? }
output_csv << clean_row.headers if row.header_row?
output_csv << clean_row
end
end
Although I largely agree with the answer of arieljuod, there are a few things that might go wrong. row.header_row? will always return false, since the return_headers: true option isn't set, thus leaving out the header. delete_if is a mutating method, so there is no need to save the result in a variable. This only returns itself so you can chain it with other methods.
The following would be enough:
read_options = {
encoding: "bom|utf-8",
skip_lines: /^#/,
headers: true,
return_headers: true,
}
CSV.open("output.csv", "w") do |output_csv|
CSV.foreach("out.csv", read_options) do |row|
row.delete_if { |header, _field| header.blank? }
output_csv << row
end
end
Note that blank? is a Ruby on Rails method, but since you've tagged the question with ruby-on-rails this should be fine.
From the CSV::new documentation (also describing CSV::foreach) options:
:return_headers
When false, header rows are silently swallowed. If set to true,
header rows are returned in a CSV::Row object with identical
headers and fields (save that the fields do not go through the
converters).

Not able to place csv data in a Hash

I have a CSV file with two columns:
PPS_Id Amount
123 100
1234 150
I read data from this file and insert in a array using the code below:
CSV.foreach("filename.CSV", headers: true) do |row|
file_details << row.inspect # hash
end
I am then trying to push the data in the file_details into a hash with PPS_Id as key and Amount as Value, I am using the code below:
file_details_hash = Hash.new
file_details.each { |x|
file_details_hash[x['PPS_Id']] = x['Amount']
}
But when I print the result I get nothing just {"PPS_Id"=>"Amount"}
Can you please help
Your code, modified to work
You need to specify the column separator for your csv, and remove inspect.
require 'csv'
file_details = []
CSV.foreach("filename.CSV", headers: true, col_sep: "\s" ) do |row|
file_details << row
end
file_details_hash = Hash.new
file_details.each { |x|
file_details_hash[x['PPS_Id']] = x['Amount']
}
p file_details_hash
#=> {"123"=>"100", "1234"=>"150"}
It now returns what you expected to get.
Shorter solution
Read the csv, drop the first line (header) and convert to a Hash :
p CSV.read("filename.CSV", col_sep: "\s").drop(1).to_h
#=> {"123"=>"100", "1234"=>"150"}
First of all, you are collecting strings into an array (see String#inspect):
file_details << row.inspect
After that you call (sic!) String#[] on that strings:
x['PPS_Id'] #⇒ "PPS_Id", because string contains this substring
That said, your code has nothing but errors. You might achieve what you want with:
csv = CSV.parse(File.read("filename.CSV"), col_sep: "\s")
csv[1..-1].to_h
#⇒ {
# "123" => "100",
# "1234" => "150"
# }
Using inspect will save your CSV rows as strings, so obviously you won't be able get what you need. Instead try this:
file_details = CSV.read("filename.csv")
Read CSV directly will create an 2D array that you can then iterate over, which will look like this: [["PPS_Id", "Amount"], ["123", "100"], ["1234", "150"]]
From there you can slightly modify your approach:
file_details.each do |key, value|
file_details_hash[key] = value
end
To receive a hash like this: {"PPS_Id"=>"Amount", "123"=>"100", "1234"=>"150"}

Ruby: Ignore last line of a file and add a row to an array

So, I have the following:
twodarray = []
File.open("SG_hum50_LODG.gro", "r") do |f|
f.each_line do |line|
textarray = line.split()
textarray[0],textarray[1],textarray[2] = textarray[1],textarray[0],textarray[2].to_i
textarray[1] = textarray[1].gsub(/:/, '').to_i
twodarray << textarray
end
end
Which works pretty well. The problem I am having is I need to ignore the last line of the text file, and I need to add
["Sensor", "Timestamp", "Sensor Value"],
As the first row in the array.
I would do this
twodarray = [["Sensor", "Timestamp", "Sensor Value"]]
File.open("SG_hum50_LODG.gro", "r") do |f|
f.each_line do |line|
textarray = line.split()
textarray[0], textarray[1], textarray[2] =textarray[1], textarray[0], textarray[2].to_i
textarray[1].gsub!(/:/, '').to_i
twodarray << textarray
end
end
twodarray.pop
twodarray << textarray unless line[f.last]
Code
def read_sensor_data(fname, first_arr)
File.open(fname, "r") do |f|
f.each_line.with_object([first_arr]) do |line, arr|
next if f.eof?
sensor, time, value, *rest = line.split
arr << [time.delete(':').to_i, sensor, value.to_i, *rest]
end
end
end
*rest is not needed if every line of the file contains no more than three words.
Example
FName = "demo"
Create a file.
File.write FName,
<<-_
heat 10:21 16
sound 11:45 74
That's all, folks!
_
#=> 49
Check the file.
puts File.read FName
heat 10:21 16
sound 11:45 74
That's all, folks!
Try the method.
read_sensor_data FName, ["Sensor", "Timestamp", "Sensor Value"]
#=> [["Sensor", "Timestamp", "Sensor Value"],
# [1021, "heat", 16],
# [1145, "sound", 74]]
Be careful aggregating lines in a file into an array because that's not a scalable solution. Only do it if you can guarantee that the file being loaded, plus the needs of your script, will never grow beyond the available free memory.
Your question isn't clear about what you're trying to do, but it sounds like you're trying to modify a file on disk, which is easily done using File.foreach to read the incoming file line-by-line.
There's a couple ways to ignore the last line, but the easiest is to count the lines in the file then read all but the last line. There are many ways to count the lines in a file, the easiest is to use the *nix wc -l command, which is designed to do it:
lines_in_file = `wc -l /path/to/file`.to_i
and for a pure Ruby solution I'd do something like this:
lines_in_file = File.foreach('input.txt').inject(0){ |i, _| i + 1 }
Then it's a simple matter of counting lines_in_file - 1 lines. This is untested but it looks about right:
INPUT_FILE = 'input.txt'
lines_in_file = File.foreach(INPUT_FILE).inject(0){ |i, _| i + 1 }
File.open('output.txt', 'w') do |fo|
fo.puts '"Sensor", "Timestamp", "Sensor Value"'
File.foreach(INPUT_FILE) do |li|
fo.puts li
break if $. == (lines_in_file - 1)
end
end

Turning spaces into underscores in CSV headers in Rails

I'm trying to upload rows from a CSV file into my database, but the spaces in the headers keep messing me up. So for example, the header will be "Order Item Id" and I want the hash key to be "order_item_id". Here's what my code looks like now:
CSV.foreach(file.path, headers:true, :header_converters => lambda { |h| h.try(:downcase) }, col_sep: ';') do |row|
product_hash = row.to_hash
product = OrderCsv.where(id: product_hash["id"])
if product.count ==1
product.first.update_attributes(product_hash)
else
user.order_csvs.create!(product_hash)
end
end
I've tried editing the product_hash with product_hash.keys.each { |k| k = "..." }
but it doesn't do anything. I've also tried creating a header converter like the one that does the downcasing, but I wasn't able to make that work either. Sorry if this is a newb question, but I've been looking everywhere for an answer and none of them have been working for me. Thanks a lot!
You can concatenate the replacement after the downcase, in the :header_converters, like this:
lambda { |h| h.try(:downcase).try(:gsub,' ', '_') }
Try this:
product_hash = { "Order Item Id" => 2 }
product_hash = product_hash.each_with_object({}) do |(k, v), h|
h[k.parameterize.underscore] = v
end
puts product_hash # {"order_item_id"=>2}
In case anyone else stumble upon this question you can make use of :symbol header_converter
https://docs.ruby-lang.org/en/2.1.0/CSV.html#HeaderConverters
The header String is downcased, spaces are replaced with underscores, non-word characters are dropped, and finally to_sym() is called.
Example:
CSV.foreach(csv_path, headers: true, header_converters: :symbol) do |row|
# do stuff
end
If you wish to convert a hash with keys containing spaces to a hash with keys containing underscores, you can do the following
hash_with_spaces = {"order item id" => '1', "some other id" => '2'}
new_hash = hash_with_spaces.inject({}) do |h, (k, v)|
h[k.gsub(' ', '_')] = v ; h
end
new_hash
#=> {"order_item_id"=>"1", "some_other_id"=>"2"}

create hash from csv file gives error

I want to create a hash, from a CSV file which has bunch of data
my ruby file code is looks like
hersteller = Hash[CSV.read("db/red.csv", col_sep: ',', row_sep: :auto, headers: true).map {|row| [row["lieferant_nr"], row["beschreibung"]]}] #this is line number 45
CSV.foreach("db/red.csv", col_sep: ',', row_sep: :auto, headers: true) do |row| # map keys
hash = Hash[row.map {|k, v| mapping[k] ? [mapping[k], v && v.strip.gsub("\u00A0", "")] : nil}.compact] # ignore NULL values
hash.reject! {|k, v| v == "NULL"} # get hersteller names
hash["hersteller"] = hersteller[hash["hersteller_nummer"]].strip.gsub("\u00A0", "") if hash["hersteller_nummer"].present? #this is line number 54
This gives me an error when i create hash
undefined method strip' for nil:NilClass
/home/anish/helios/dynalink/db/seeds.rb:54:inblock in top (required)>'
/home/anish/helios/dynalink/db/seeds.rb:46:in top (required)>'
Can anyone suggest me what's went wrong here....
Thanks in advance
change:
hash["hersteller"] = hersteller[hash["hersteller_nummer"]].strip.gsub("\u00A0", "") if hash["hersteller_nummer"].present? #this is line number 54
to:
hash["hersteller"] = hersteller[hash["hersteller_nummer"]].strip.gsub("\u00A0", "") if hersteller[hash["hersteller_nummer"]].present?
you should be checking for presence of hersteller[hash["hersteller_nummer"]] and not hash["hersteller_nummer"]

Resources