1

I have a doubt about showing a generated CSV file to the user (with a large amount of data). So here is the task I have to do.

App: I have a film that has many characters.

Task:

  • allow users to upload characters via CSV (ok, done)
  • if there are errors, show them for each row (ok, done)
  • in the results page, also show a link to a new CSV file only with the remaining characters - the ones that couldn’t be created (I’m stuck here)

Here is part of my code (upload method):

def upload
    saved_characters = []
    characters_with_errors = []
    errors = {}

    begin
      CSV.parse(params[:csv].read, **csv_options) do |row|
        row_hash = clear_input(row.to_h)
        new_character = Character.new(row_hash)

        if new_character.save
          add_images_to(new_character, row)
          saved_characters << new_character
        else
          characters_with_errors << new_character
          errors[new_character.name] = new_character.errors.full_messages.join(', ')
        end
      end
    rescue CSV::MalformedCSVError => e
      errors = { 'General error': e.message }.merge(errors)
    end

    @upload = {
      errors: errors,
      characters: saved_characters,
      characters_with_errors: characters_with_errors
    }
  end

The issue: large amount of data

In the end, the upload.html.erb almost everything works fine, it shows the results and errors per column BUT I’m not sure how create a link on this page to send the user to the new CSV file (only with characters with errors). If the link sends the user to another method / GET endpoint (for the view with CSV format), how can I send such a large amount of data (params won’t work because they will get too long)? What would be the best practice here?

  • If I understand this correctly you want to have a separate page to edit **characters_with_errors** but you are concerned that that data might be too large for the params? – Haumer Oct 03 '22 at 16:32
  • @Haumer , not exactly to edit them. I want to show a link so the user can download a new CSV file with the unsaved characters, so they can upload them again. For that, I believe I need a link_to another method, sending the information about those **characters_with_errors** in order to generate and show the CSV. But in this situation I can have more than 1000 characters. – Gustavo Borges Oct 03 '22 at 16:47
  • Could you save the **characters_with_errors** as a csv and then create the download link? – Haumer Oct 03 '22 at 17:11
  • @Haumer, Yes, I could. Do you mean saving the CSV in the database? How can I do that? (I'm using Postgres). Maybe as a text field... – Gustavo Borges Oct 03 '22 at 18:01
  • If i recall correctly you can save csvs to cloudinary? but even if i misremember you could just save the relevant data as a string or w/e and then rebuild the csv once you need it – Haumer Oct 03 '22 at 18:18

1 Answers1

0

You can use a session variable to store the data, and then redirect to a new action to download the file. In the new action, you can get the data from the session variable, and then generate the CSV file.

For example, In the upload action, you can do something like this:

session[:characters_with_errors] = characters_with_errors

redirect_to download_csv_path

In the download_csv action, you can do something like this:

characters_with_errors = session[:characters_with_errors]

session[:characters_with_errors] = nil

respond_to do |format|
  format.csv { send_data generate_csv(characters_with_errors) }
end

In the generate_csv method, you can do something like this:

def generate_csv(characters_with_errors)
  CSV.generate do |csv|
    csv << ['name', 'age' ]
    characters_with_errors.each do |character|
      csv << [character.name, character.age]
    end
  end
end

Another option, you can use a temporary file to store the data and then send the user to the new CSV file. Here is an example:

  def upload
      saved_characters = []
      characters_with_errors = []
      errors = {}
  
      begin
        CSV.parse(params[:csv].read, **csv_options) do |row|
          row_hash = clear_input(row.to_h)
          new_character = Character.new(row_hash)
  
          if new_character.save
            add_images_to(new_character, row)
            saved_characters << new_character
          else
            characters_with_errors << new_character
            errors[new_character.name] = new_character.errors.full_messages.join(', ')
          end
        end
      rescue CSV::MalformedCSVError => e
        errors = { 'General error': e.message }.merge(errors)
      end
  
      @upload = {
        errors: errors,
        characters: saved_characters,
        characters_with_errors: characters_with_errors
      }
  
      respond_to do |format|
        format.html
        format.csv do
          # Create a temporary file
          tmp = Tempfile.new('characters_with_errors')
          # Write the CSV data to the temporary file
          tmp.write(characters_with_errors.to_csv)
          # Send the user to the new CSV file
          send_file tmp.path, filename: 'characters_with_errors.csv'
          # Close the temporary file
          tmp.close
        end
      end
    end
Tibic4
  • 3,709
  • 1
  • 13
  • Are you sure this is a solution for this question? can the rails session hash store more data than you can send via params? (i think its 4kb and 8kb respectively) – Haumer Oct 03 '22 at 19:57
  • Yes, you can store more data in the session variable than you can send via params. The session variable is stored in the cookie, and the cookie can store up to 4KB of data. So, you can store a lot of data in the session variable. – Tibic4 Oct 03 '22 at 20:01
  • https://stackoverflow.com/questions/2659952/maximum-length-of-http-get-request this would suggest http get requests are between 2kb and 8kb - so roughly equal or larger, or am i wrong? – Haumer Oct 03 '22 at 20:03
  • I added another option. – Tibic4 Oct 03 '22 at 20:03
  • yey you came to the same conclusion I did ;) – Haumer Oct 03 '22 at 20:05
  • 1
    I have to search more deeply. I think you are wrong. The session variable is stored in the cookie, and the cookie can store up to 4KB of data. But the URL can only store up to 2KB of data. – Tibic4 Oct 03 '22 at 20:09
  • It is what I think. But I don't own the truth. I'll search calmly. If you have any news, please let me know. – Tibic4 Oct 03 '22 at 20:13
  • so is that other stackoverflow post, the one that suggests 2kb-8kb, wrong too? it reads: **Most web servers have a limit of 8192 bytes (8 KB), (...)** – Haumer Oct 03 '22 at 20:13
  • I don't think it's wrong. I'll look into the matter and let you know. But it's an excellent question. I'm curious now. – Tibic4 Oct 03 '22 at 20:18
  • I've tried the session option and I get `raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE`. I've checked and `MAX_COOKIE_SIZE = 4096`. Just as a comparison, `options[:value].bytesize = 2935802 `. I will test the Tempfile next. – Gustavo Borges Oct 05 '22 at 16:20