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 'アップロード'
これはhaml。
csv_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で読み込もうとしたところ、失敗したからだ。