P学習帳

書いておぼえるブログ

Ruby on RailsでアップロードしたCSVを読み込んでDBに書き込む

やりたいこと

ローカルにあるCSVをWEBアプリにアップロードし、DBに書き込む。CSVファイルの文字コードはcp962を想定している。

コード

View

= form_tag csv_upload_path, multipart: true do
    = file_field_tag :file
    = submit_tag 'アップロード'

これはhamlcsv_upload_pathのところはactionに相当するものを書く。アクション先のエイリアスはアプリのルートに/infoを付けるか、コンソールでrake routesとやってルーティングの一覧が得られる。

CSVファイルをアップロードするとき、ファイルそのものが特定のモデルと対応するわけではないので、form_tagを使うのがよいのだと思う。

file_field_tagの引数:fileは、HTMLのnameタグに変換される。コントローラーでこのテキストエリアの内容を受け取るには、params['file']とすればいい。

Controller

次、コントローラー。ビューから送ったCSVファイルを受け取る。params[:file]でファイル情報が取れる。
ここではProductモデルがあるものとし、このモデルに書いたimportメソッドでファイル内容の読み出しと書き込みをする。
そのあとのリダイレクトでは、ビューに表示するメッセージをnoticeに渡している。こうすることで、ビューにnoticeに対応するスニペットを書いておけば、コントローラーで指定したメッセージを画面に表示させることができる。

  def import_btob
    Product.import(params[:file])
    redirect_to root_url, notice: "CSVファイルをアップロードしました。"
  end

Model

コントローラーから呼び出すメソッドを書く。CSVファイルの読み込みとDBへの書き込みをおこなう。

ここでは、読み込みと書き込みのメソッドを別ける。importメソッドとopen_spreadsheetメソッドである。

class Product < ApplicationRecord  

def self.import(file)
    spreadsheet = open_spreadsheet(file)
    header = ["goods_identification_type", "goods_label", "goods_id", "option_1_id", "option_2_id", "selling_price", "quantity", "option_uniq_code", "jan_code"]

    ActiveRecord::Base.transaction do
      (2..spreadsheet.count).each do |i|
        if !spreadsheet[i].nil?
          row = Hash[[header, spreadsheet[i] ].transpose]
          option = Product.new(row)
          option.save!
        end
      end
    end
  end

肝は、Hash[ [header, spreadsheet[i] ].transpose]だ。こうすると、ヘッダーの要素名をキー、CSVのレコード各要素をバリューとするハッシュができる。それをnewすることでDBにレコードを書き込める。

読み込み処理はこちらでおこなう。

  def self.open_spreadsheet(file)
    case File.extname(file.original_filename)
    when '.csv'  then
      CSV.parse(File.read(file.path, encoding: 'cp932').encode("UTF-8", :invalid => :replace))
    else raise "Unknown file type: #{file.original_filename}"
    end
  end

end

CSVの読み込みを一行でやっている。このスニペットはQiitaから拾った。とてもいい。

CSV.parse(File.read(file.path, encoding: 'cp932').encode("UTF-8", :invalid => :replace))

エンコードをcp932にしたのは、はじめRooというgemで読み込もうとしたところ、失敗したからだ。