I have a Ruby on Rails application with which wish to import a user defined CSV file, manipulate some aspects of that file, and then use it to create objects in my database. My controller action looks something like this:
def validate_annual_sales
uploaded_file = params[:client][:annual_sales_file]
filename = filename_with_timestamp(uploaded_file.original_filename)
file_path = Rails.root.join('files', 'uploads', filename)
File.open(file_path, 'wb') { |file| file.write(uploaded_file.read) }
@fyear = 2024
@season_desc = "Season 2024"
handler = SpektrixReportHandler.new(@client.id, file_path.to_s, @fyear, @season_desc)
@validation_errors = handler.validate_csv
unless @validation_errors.any?
# perform import
handler.translate_csv_to_data
File.delete(file_path) if File.exist?(file_path)
redirect_to historicals_client_url(@client), notice: 'Annual Sales Report information successfully imported.'
else
end
rescue => ex
AppSignaler.set_client_error(ex, @client.try(:id))
File.delete(file_path) if File.exist?(file_path)
redirect_to upload_annual_sales_client_url(@client), alert: ex.message
end
The annual_sales_file
parameter is a file_field
in the view that we use in other portions of the application for this same purpose, which work fine. Similarly with the opening and reading the file.
The relevant portions of the service object I have processing this situation are:
def validate_csv
json_schema_validator = mapping.dig(:schema_validator)
errors = []
get_parsed_csv.each.with_index(1) do |row, index|
data = row.to_hash
json_errors = JSON::Validator.fully_validate(json_schema_validator, data.to_json, errors_as_objects: true)
if json_errors.empty?
# don't try to validate the values if the basic json validations failed
csv_validator = CsvValidator.validate(data, mapping)
unless csv_validator[:errors].try(:empty?)
errors << ["Error processing row #{index}:"] + csv_validator[:errors]
end
else
cleaned_errors = clean_json_errors(json_errors)
errors << ["Error processing row #{index}:"] + cleaned_errors
end
end
errors
end
def translate_csv_to_data
blank_values = [:fiscal_year, :season_description].select{ |attr_name| self.send(attr_name).blank? }.map{|value| value.to_s.titleize}
if blank_values.any?
raise "Missing #{blank_values.to_sentence}"
end
get_parsed_csv.each.with_index(1) do |row, index|
ap "row #{index}"
hashed_row = row.to_hash
row = parser_factory.parse(hashed_row, mapping)
row[:client_id] = client.id
row[:fyear] = fiscal_year
row[:season_desc] = season_description
row[:sin_amount] = row.delete(:sin_amt)
row[:pkg_amount] = row.delete(:pkg_amt)
HistoricalDaily.create!(row)
end
ensure
File.delete(file_path) if File.exist?(file_path)
end
private
# from EmailScrapperLogger#get_parsed_csv
def get_parsed_csv
CSV.parse(File.read(csv, encoding: 'bom|utf-8'), headers: true, force_quotes: true, header_converters: :symbol)
end
However, when I actually choose a file and run this through, I get a ActionDispatch::Cookies::CookieOverflow
exception raised that crashes the page.
As far as I can tell, I'm not manipulating the given file at all, so I am very confused as to where the cookies are even being changed. I have tried loading everything into memory as a hash and manually creating the objects from that data. I have tried loading it in as a zip file, extracting the csv file into another directory entirely and going from there. That's how we do it elsewhere, and it seems to work fine there. Not sure what I'm doing differently here.
Please help!
I did finally figure this out. As mentioned in the comment, it was indeed something completely different that didn't have to do with manipulating the cookies or even session directly.
What happened was that an error occurred elsewhere. One of those silly NoMethodErrors that are pretty obvious once you see the message and the backtrace. The difficulty in this case was that there was a line of code in the controller which printed out an error message to the browser. It put that message in the flash object, which is part of the session object, which counts as a cookie. So, when that error was thrown, and the naive error.message
was put in the flash object, a serialized version of a model object was put in there too, quickly overflowing the cookie limit. Very indirect and very subtle.
In retrospect, I included a hint to the answer in my question with this line of controller code:
redirect_to upload_annual_sales_client_url(@client), alert: ex.message
Pretty innocent at first glance, and the danger and implications were not noticed by me or the person who kindly took the time to comment here (I appreciate you!). Hopefully someone else will have this problem and find this helpful!