使用ActiveAdmin在Rails应用程序中导入CSV数据

29

我想通过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数据的人吗?

7个回答

48

Thomas Watson给出的很好的答案基础上,我找到了方向并解决了问题。

以下代码不仅允许对示例“Posts”模型进行CSV上传,而且可用于之后的任何模型。 只需将示例中的action_item和collection_actions复制到任何其他ActiveAdmin.register块中,功能就是相同的。希望这可以帮到你。

app/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

app/models/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

注意:这个例子会检查第一列是否为ID列,如果是,Rails会给新对象分配一个ID(参见下面的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"

注意:引号不总是必需的。


对于csv_db块,您是否将model_name、target_model和new_object替换为类似于Post或Lead的相关名称? - TJ Sherrill
1
当您调用CsvDb.convert_save("post", params[:dump][:file])时,只需将post替换为您想要的资源即可。 - ben.m
如何覆盖现有的条目?如果我尝试在现有记录上导入CSV文件,会出现“SQLite3::ConstraintException: PRIMARY KEY must be unique”错误。 - Slicekick
问题在于它的 Id。你可以添加一个条件语句,如: 如果 id == something.id something.update_attributes :blah => "blah" 结束 - ben.m

15

添加一个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

9

@krhorst,我试图使用你的代码,但是在大批量导入时效果不佳。它会占用太多内存=( 因此,我决定使用基于activerecord-import gem的自己的解决方案。

这里有https://github.com/Fivell/active_admin_import

特点

  1. 编码处理
  2. 支持使用ZIP文件导入
  3. 两步导入(见example2)
  4. CSV选项
  5. 自动添加CSV标题的能力
  6. 批量导入(activerecord-import)
  7. 自定义模板的能力
  8. 回调函数支持
  9. 支持从zip文件导入
  10. ....

3

基于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的答案,以防我做错了什么严重的事情。


1
针对大型Excel文件,由于其正常处理需要耗费时间,我创建了一个gem,该gem使用活动作业来处理Excel表格,并使用Action Cable(基于Websockets的实时应用程序框架)来显示结果。

https://github.com/shivgarg5676/active_admin_excel_upload


0
扩展ben.m的有用回答。
我在CSV导入逻辑方面遇到了问题(属性不匹配和列迭代器未按要求运行),并实施了一种更改,它使用每行循环和model.create方法。这允许您导入与属性匹配的标题行的.csv文件。

app/models/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

0

上述一些解决方案效果不错。 在实践中,我遇到了挑战,以下是我解决的问题:

  1. 导入具有不同顺序列的CSV数据
  2. 防止Excel CSV中隐藏字符导致的错误
  3. 重置数据库主键,以便应用程序在导入后可以继续添加记录

注意:我删除了ID过滤器,以便我可以更改我正在处理的ID,但大多数用例可能希望保留它。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接