web-dev-qa-db-ja.com

ActiveAdminを使用してRails AppにCSVデータをインポートする

activeadminパネルからCSVファイルをアップロードしたい。

リソース「product」のインデックスページで、「importcsvfile」のある「newproduct」ボタンの横にボタンが必要です。

どこから始めればいいのかわからない。ドキュメントには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データをインポートできる人はいますか?

26
user993460

Thomas Watsonsからの続きは、残りの部分を理解する前に自分の方向性を理解するのに役立った答えへの素晴らしいスタートです。

コードブローでは、投稿モデルの例だけでなく、それ以降のモデルの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"

注:引用符は必ずしも必要ではありません

42
ben.m

collection_actionを追加しても、そのアクションにリンクするボタンは自動的に追加されません。インデックス画面の上部にボタンを追加するには、次のコードをActiveAdmin.registerブロックに追加する必要があります。

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

ただし、質問に投稿した収集アクションを呼び出す前に、まずユーザーがアップロードするファイルを指定する必要があります。私は個人的に別の画面でこれを行います(つまり、twoコレクションアクションを作成します-1つは:getアクションで、もう1つは: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
13
Thomas Watson

@krhorst、私はあなたのコードを使おうとしていましたが、残念ながらそれは大きなインポートを吸います。それはとても多くのメモリを消費します=(それで私はactiverecord-importgemに基づいた独自のソリューションを使用することにしました

ここにあります https://github.com/Fivell/active_admin_import

特徴

  1. エンコーディング処理
  2. Zipファイルでのインポートをサポート
  3. 2段階のインポート(example2を参照)
  4. CSVオプション
  5. CSVヘッダーを自動的に付加する機能
  6. 一括インポート(activerecord-import)
  7. テンプレートをカスタマイズする機能
  8. コールバックのサポート
  9. Zipファイルからのインポートをサポート
  10. ..。
6
Fivell

将来の参考のために、アクティブな管理リソースにcsvインポートを簡単に追加できるgemを作成しました

このリンクを参照

2
krhorst

上記の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の答えが汚染されることは望ましくありませんでした。

2
ScottJShea

通常の処理に時間がかかる大きなExcelの場合、アクティブなジョブを使用してExcelシートを処理し、アクションケーブル(websockets)を使用して結果を表示するgemを作成しました

https://github.com/shivgarg5676/active_admin_Excel_upload

0
shiv garg

私が非常に有用だと思った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
jeffcchau

上記の解決策のいくつかはかなりうまくいきました。私は実際に、以下で解決した課題に遭遇しました。解決された問題は次のとおりです。

  1. 異なる順序の列を持つCSVデータのインポート
  2. ExcelCSVの隠し文字によるエラーの防止
  3. インポート後もアプリケーションがレコードを追加し続けることができるように、データベースのprimary_keyをリセットします

注:作業中の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
0
Misha Herscu