Импорт данных CSV в приложение Rails с помощью ActiveAdmin

я хочу загрузить файлы CSV через активную панель администратора.

на индексной странице ресурса "продукт" я хочу кнопку рядом с кнопкой "новый продукт" с "импортом CSV-файла".

я не знаю, с чего начать. в документации есть что-то про collection_action, но с приведенным ниже кодом у меня нет ссылки вверху.

ActiveAdmin.register Post do
    collection_action :import_csv, :method => :post do
      # Do some CSV importing work here...
      redirect_to :action => :index, :notice => "CSV imported successfully!"
    end
  end

кто-нибудь здесь, кто использует activeadmin и может импортировать данные csv?


person user993460    schedule 13.10.2011    source источник


Ответы (7)


Продолжая от Томаса Уотсона, отличного начала к ответу, который помог мне сориентироваться, прежде чем понять остальное.

Удар по коду позволяет не только загружать CSV для примера модели Posts, но и для любых последующих моделей после этого. все, что вам нужно сделать, это скопировать action_item и collection_actions из примера в любой другой блок ActiveAdmin.register, и функциональность будет такой же. надеюсь это поможет.

приложение/admin/posts.rb

ActiveAdmin.register Post do
  action_item :only => :index do
    link_to 'Upload CSV', :action => 'upload_csv'
  end

  collection_action :upload_csv do
    render "admin/csv/upload_csv"
  end

  collection_action :import_csv, :method => :post do
    CsvDb.convert_save("post", params[:dump][:file])
    redirect_to :action => :index, :notice => "CSV imported successfully!"
  end

end

приложение/модели/csv_db.rb

require 'csv'
class CsvDb
  class << self
    def convert_save(model_name, csv_data)
      csv_file = csv_data.read
      CSV.parse(csv_file) do |row|
        target_model = model_name.classify.constantize
        new_object = target_model.new
        column_iterator = -1
        target_model.column_names.each do |key|
          column_iterator += 1
          unless key == "ID"
            value = row[column_iterator]
            new_object.send "#{key}=", value
          end
        end
        new_object.save
      end
    end
  end
end

примечание: в этом примере проверяется, является ли первый столбец столбцом идентификатора, затем он пропускает этот столбец, поскольку рельсы назначат идентификатор новому объекту (см. пример CSV ниже для справки)

app/views/admin/csv/upload_csv.html.haml

= form_for :dump, :url=>{:action=>"import_csv"}, :html => { :multipart => true } do |f|
  %table
    %tr
      %td
        %label{:for => "dump_file"}
          Select a CSV File :
      %td
        = f.file_field :file
    %tr
      %td
        = submit_tag 'Submit'

app/public/example.csv

"1","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"2","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"3","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"4","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"5","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"

примечание: кавычки не всегда нужны

person ben.m    schedule 25.03.2012
comment
для блока csv_db вы заменяете model_name, target_model и new_object относительными именами, такими как Post или Lead? - person TJ Sherrill; 25.05.2012
comment
вам не нужно просто, когда вы вызываете CsvDb.convert_save(post, params[:dump][:file]) просто заменить сообщение ресурсом, который вы хотели бы - person ben.m; 02.06.2012
comment
Как перезаписать существующие записи? Я получаю ошибку SQLite3::ConstraintException: PRIMARY KEY must be unique, если пытаюсь импортировать CSV поверх существующих записей. - person Slicekick; 01.07.2012
comment
его идентификатор, который является проблемой. вы можете добавить условный оператор в виде строки if id == something.id something.update_attributes :blah =› blah end - person ben.m; 25.07.2012

Добавление collection_action не приводит к автоматическому добавлению кнопки, связанной с этим действием. Чтобы добавить кнопку в верхней части индексного экрана, вам нужно добавить следующий код в блок ActiveAdmin.register:

action_item :only => :index do
  link_to 'Upload CSV', :action => 'upload_csv'
end

Но прежде чем вызывать действие по сбору, которое вы разместили в своем вопросе, вам сначала нужно, чтобы пользователь указал, какой файл нужно загрузить. Я лично сделал бы это на другом экране (т. е. создал бы два действия по сбору — одно действие :get, а другое — действие :post). Таким образом, полный контроллер AA будет выглядеть примерно так:

ActiveAdmin.register Post do
  action_item :only => :index do
    link_to 'Upload posts', :action => 'upload_csv'
  end

  collection_action :upload_csv do
    # The method defaults to :get
    # By default Active Admin will look for a view file with the same
    # name as the action, so you need to create your view at
    # app/views/admin/posts/upload_csv.html.haml (or .erb if that's your weapon)
  end

  collection_action :import_csv, :method => :post do
    # Do some CSV importing work here...
    redirect_to :action => :index, :notice => "CSV imported successfully!"
  end
end
person Thomas Watson    schedule 13.10.2011

@krhorst, я пытался использовать ваш код, но, к сожалению, это отстой при большом импорте. Он съедает так много памяти =( Поэтому я решил использовать собственное решение, основанное на геме activerecord-import.

Вот он: https://github.com/Fivell/active_admin_import

Функции

  1. Обработка кодирования
  2. Поддержка импорта с ZIP-файлом
  3. Двухэтапный импорт (см. пример 2)
  4. Параметры CSV
  5. Возможность автоматически добавлять заголовки CSV
  6. Массовый импорт (activerecord-import)
  7. Возможность настройки шаблона
  8. Поддержка обратных вызовов
  9. Поддержка импорта из zip-файла
  10. ....
person Fivell    schedule 11.03.2013

Основываясь на отличном ответе от ben.m выше, я заменил предложенный раздел csv_db.rb следующим:

require 'csv'
class CsvDb
  class << self
    def convert_save(model_name, csv_data)
      begin
        target_model = model_name.classify.constantize
        CSV.foreach(csv_data.path, :headers => true) do |row|
          target_model.create(row.to_hash)
        end
      rescue Exception => e
        Rails.logger.error e.message
        Rails.logger.error e.backtrace.join("\n")
      end
    end
  end
end

Хотя это и не полный ответ, я не хотел, чтобы мои изменения загрязняли ответ ben.m на случай, если я сделаю что-то вопиющее неправильно.

person ScottJShea    schedule 09.07.2013

расширяя ответ ben.m, который я нашел очень полезным.

У меня были проблемы с логикой импорта CSV (атрибуты не выравниваются, а итератор столбцов не работает должным образом), и я реализовал изменение, которое вместо этого использует построчный цикл и метод model.create. Это позволяет импортировать файл .csv со строкой заголовка, соответствующей атрибутам.

приложение/модели/csv_db.rb

require 'csv'
class CsvDb
  class << self
    def convert_save(model_name, csv_data)
      csv_file = csv_data.read
      lines = CSV.parse(csv_file)
      header = lines.shift
      lines.each do |line|
        attributes = Hash[header.zip line]
        target_model = model_name.classify.constantize
        target_model.create(attributes)
      end
    end
  end
end

Таким образом, ваш импортированный файл CSV может выглядеть так (используйте для сопоставления с атрибутами модели):

importExample.csv

first_name,last_name,attribute1,attribute2
john,citizen,value1,value2
person jeffcchau    schedule 28.08.2014

Для большого Excel, который требует времени в обычном процессе, я создал гем, который обрабатывает листы Excel с использованием активного задания и отображает результаты с помощью кабеля действия (веб-сокеты).

https://github.com/shivgarg5676/active_admin_excel_upload

person shiv garg    schedule 27.08.2017

Некоторые из приведенных выше решений работали довольно хорошо. На практике я столкнулся с проблемами, которые я решил здесь ниже. Решаемые проблемы:

  1. Импорт данных CSV со столбцами в разном порядке
  2. Предотвращение ошибок, вызванных скрытыми символами в файлах Excel CSV
  3. Сброс базы данных primary_key, чтобы приложение могло продолжать добавлять записи после импорта

Примечание. Я убрал фильтр идентификаторов, чтобы можно было изменить идентификаторы того, над чем я работаю, но в большинстве случаев, вероятно, следует оставить его.

require 'csv'
class CsvDb
  class << self
    def convert_save(model_name, csv_data)
      csv_file = csv_data.read
      csv_file.to_s.force_encoding("UTF-8")
      csv_file.sub!("\xEF\xBB\xBF", '')
      target_model = model_name.classify.constantize
      headers = csv_file.split("\n")[0].split(",")
      CSV.parse(csv_file, headers: true) do |row|
        new_object = target_model.new
        column_iterator = -1
        headers.each do |key|
          column_iterator += 1
          value = row[column_iterator]
          new_object.send "#{key.chomp}=", value
        end
        new_object.save
      end
      ActiveRecord::Base.connection.reset_pk_sequence!(model_name.pluralize)
    end
  end
end
person Misha Herscu    schedule 14.09.2019