こんにちは、さもです。
CSVをアップロードしてデータの一括投入機能はよくあると思いますが、最も基本的な実装をメモしておきます。
イメージはこんな感じです。
インポートにはactiverecord-importを使ってバルクインサートしています。
スポンサーリンク
Model
サンプルとして、次のようなEmailテーブルを作ってみましょう
- マイグレーション
class CreateEmails < ActiveRecord::Migration def change create_table :emails do |t| t.string :email, null: false t.string :name t.timestamps null: false end end end
- モデル
class Email < ActiveRecord::Base validates :email, presence: true end
View
次にファイルアップロード部分です。
- emails/new.html.erb
<% if flash[:notice] %> <div class="alert alert-info" role="alert"><%= flash[:notice] %></div> <% end %> <div> <%= form_tag emails_path, method: :post, multipart: true do |f| %> <div class="search_item"> <%= text_field_tag 'filename',"", id: "filename", disabled: true %> <%= file_field_tag 'emails_file', id: "file_input", style: "display: none;", onchange: "file_selected($(this));" %> <%= button_tag 'ファイル選択', class: %i(btn-primary csv_input_btn), type: 'button', onclick: "$('#file_input').click();" %> </div> <div> <button type="submit" class="btn btn-primary">CSVインポート</button> </div> <% end %> </div> <script type="text/javascript"> function file_selected(file_field){ var filename = $(file_field)[0].files[0].name; $("#filename").val(filename); } </script>
ファイル選択のところで、text_field, file_field, buttonの三つがあるパターンはよくあると思います。
file_field自体がファイル選択ボタンを表示させるのですが、デザインがあまりいけていないです。
なので、かっこよくデザインされたボタンを新たに追加して、このボタンが押されたら、隠してあるファイル選択を実行します。
disabledになっているテキストフィールドは、選択したファイル名の表示用です。
ファイルを選択すると、file_fieldのonchangeが発火し、file_selected関数が呼ばれます。
file_selected関数は単に、選択されたファイルの名前をテキストフィールドに表示しています。
(注)忘れがちなのですが、ファイルを送る場合は、form_tag のなかでmultipart: trueを指定してください。
ビューはこんな感じです。
Gemfile
冒頭でも言いましたが、バルクインサートしたいので、Gemfileに次を追記して、bundleしておきます
# bulk insert gem 'activerecord-import'
バルクインサートとは、複数のレコードを登録する際に、1レコードごとにinsertするのではなく、まとめてinsertする機能です。
SQLを生成してinsert、というのを繰り返さなくていいので、レコード数が多いときに特に登録処理が速くなります。
Controller
ではサーバでの処理を作っていきましょう
class EmailsController < ApplicationController def new end def create registered_count = import_emails redirect_to emails_path, notice: "#{registered_count}件登録しました" end private def import_emails # 登録処理前のレコード数 current_email_count = ::Email.count emails = [] # windowsで作られたファイルに対応するので、encoding: "SJIS"を付けている CSV.foreach(params[:emails_file].path, headers: true, encoding: "SJIS") do |row| emails << ::Email.new({ name: row["name"], email: row["email"] }) end # importメソッドでバルクインサートできる ::Email.import(emails) # 何レコード登録できたかを返す ::Email.count - current_email_count end end
単純にcsvの各行からEmailのインスタンスを作り、emails へ挿入しています。
activerecord-importでは、インスタンスの配列をimportメソッドによってバルクインサートしてくれます。
登録に成功すると、import_emailsは登録したレコード数を返すので、その数を画面に表示します。
まとめ
簡単でしたがCSVアップロード&一括登録の実装でした。
そういえばこの前、あるエラーの対処法を探していてあるブログを見つけて無事解決できたのですが、そのブログが、このブログでした。
168日前の自分ありがとう!
この記事もまた自分や他のエンジニアの方々の役に立ってくれることを願います。
読者登録はこちらからお願いします。
Ruby on Rails 5アプリケーションプログラミング
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/14
- メディア: 大型本
- この商品を含むブログを見る
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2016/12/17
- メディア: 単行本
- この商品を含むブログを見る