require 'csvscan' module CSVScan class ParseError < Exception; end def self.foreach(file_path, options = {}) parsed_lines, parsed_rows, total_records = 0, 0, 0 first_line, headers = true, Hash.new # CSVScan doesn't parse the last line, so make a blank one to make sure nothing is missed file_contents = (File.read(file_path) + "\n") CSVScan.scan(file_contents) do |row_array| parsed_lines += 1 # total lines, regardless of whether they have content next if row_array == [nil] || row_array.nil? # Skip blank lines parsed_rows += 1 # total rows, regardless of whether they are headers if options[:headers] == :first_line if first_line headers = row_array first_line = false else row_hash = Hash.new row_array.each_with_index { |r, i| row_hash[headers[i]] = r } yield(row_hash) total_records += 1 end else yield(row_array) total_records += 1 end end if parsed_lines < file_contents.lines.count raise ParseError.new(" Expected to parse #{file_contents.lines.count} lines but only parsed #{parsed_lines}. Invalid CSV row around lines #{[1, parsed_lines].max} to #{parsed_lines + 1}? ".squish) end { :lines => parsed_lines, :rows => parsed_rows, :records => total_records } end end