Best way to do CSV seed file upload to rails repo through administrate?

638 Views Asked by At

The main table in my backend gets populated by a CSV file. When seeding and running rails db:seed , the CSV files in lib/assets/csv is read with file = File.read(Rails.root.join('lib', 'assets', 'csv', 'data.csv'), and the logic in seeds.rb runs through each row to create a table entry, populating the fields with the CSV column data.

I've also implemented Thoughtbot's Administrate for a UI admin dashboard to view this data.

So my question is, what is the best way to configure some kind of custom file upload system on Administrate dashboard if I ever need to replace the files sitting in lib/assets/csv and reseed?

I've looked at ActiveStorage but I've only ever used it to store files like an image specifically related to a table entry, not for seeding the entire table.

1

There are 1 best solutions below

0
On

Yes, it is possible, and you don't need ActiveStorage. Rails's basic file upload facilities will be enough. Meanwhile, Administrate tries to follow Rails conventions, and provides hooks for you to alter the templates and refer to your own controllers and actions where you can implement this. Here's an example of how you could solve your problem.

First, you'll want to add a form with a file field. Admins will be able to use it to upload CSVs.

There are many places where you could put this form. For this example, let's say that it's "products" that you want to import (model Product), and you want to put the link in the index page.

You can override Administrate's own templates with your own ones. The following command will generate a copy of the index template that you can customize and Administrate will use instead of its own:

$ ./bin/rails g administrate:views:index

This will get you a new file app/views/admin/application/index.html.erb.

Alter it to add a link to a separate page that will host the upload form:

  <% if page.resource_name == "product" %>
    <div class="link-to-upload"><%= link_to "CSV Import", upload_admin_products_path %></div>
  <% end %>

You can put this in between the page title and the search bar. Or wherever you want, really. It does the following:

  • The if checks that we are in the index page for "products" and not for something else.
  • The link_to links to a page that we haven't created yet, which will host the upload form.

If you load the products index page now, it will show an error NameError, with message undefined local variable or method 'upload_admin_products_path'. This is because we still don't have a route for this new page of ours. Lets add it now.

In the routes file config/routes.rb, you'll already have a route resources :products. Change it to look like this:

    resources :products do
      collection do
        get :upload, action: 'upload_form'
        post :upload, action: 'upload_process'
      end
    end

This actually adds two routes, not one. One will be for the form page, while the other one will be for the action that will process the uploaded CSV.

For now, you can click on the link and go to the new page. It will break again. This time the error will be Unknown action - The action 'upload_form' could not be found for Admin::ProductsController. That's because we haven't provided a view for this page yet. Let's do it now.

Add a view with the following contents at app/views/admin/products/upload_form.html.erb:

<header class="main-content__header" role="banner">
  <h1 class="main-content__page-title" id="page-title">
    Import products from CSV
  </h1>
</header>

<section class="main-content__body main-content__body">
  <%= form_for :products, html: { class: "form" } do |f| %>
    <p><%= f.file_field(:file) %></p>
    <p><%= f.submit "Upload" %></p>
  <% end %>
</section>

Now you should be able to load the page and see the form. I have added additional markup (like those header and section elements) to make it look similar to other pages that Administrate provides.

That form has a file_field and a submit button. On submit, it will send the file to the current URL (/admin/products/upload) as a POST request. It will be handled by the post route we added earlier, on the action upload_process. Let's write that now.

On the controller Admin::ProductsController, add the following method to implement the action:

    def upload_process
      file = params[:products][:file]
      data = CSV.parse(file.to_io, headers: true, encoding: 'utf8')

      # Start code to handle CSV data
      ActiveRecord::Base.transaction do
        data.each do |row|
          Product.create(row.to_h)
        end
      end
      # End code to handle CSV data

      redirect_to admin_products_url
    end

This will create new products based on what's contained in the CSV, and then will redirect the user back to the products index page.

This example action is very simple and may not suit your needs exactly. You probably want to copy your code from seeds.rb and paste it in between my Start and End comments, so that it does what you want exactly. There are many possibilities.