読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RailsでCSV/Excel/OpenOfficeのアップロード機能の実装方法

CSV/Excel

RailsでCSV/Excel/OpenOfficeのアップロード機能の実装方法について説明します。

動作確認

  • Ruby 2.1
  • Rails 4.1
  • Roo 1.13.2

目次

  1. Railsプロジェクトの作成
  2. CSVのインポート機能の実装
  3. Excelのインポート機能の実装

1. Railsプロジェクトの作成

まず、Raislのプロジェクトを作成します。

rails new csv_import_test
cd csv_import_test

そして、必要なコントローラー、ビュー、モデルを作成します。

rails g controller Products index
rails g model Product name:string price:integer released_on:date
rake db:migrate


CSV出力するためのデータを作成します。

# db/seeds.rb

names = %w(レコーダー イヤホン マイク Webカメラ 洗濯機 冷蔵庫 エアコン ノートPC 40型TV デジタルカメラ)

names.each do |name|
  random = [*1..10].sample  # 1から10のランダム値を取得
  Product.create! name: name, price: random * 1000, released_on: random.day.ago
end

DBにデータを投入します。

rake db:seed

そして、コントローラーのindexアクションを実装します。

# app/contollers/products_contoller.rb
class ProductsController < ApplicationController
  def index
    @products = Product.all
  end
end


そして、ビューを作成します。

# app/views/products/index.html.erb
<h1>商品一覧</h1>

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>名前</th>
      <th>値段</th>
      <th>発売日</th>
    </tr>
  </thead>

  <tbody>
    <% @products.each do |product| %>
      <tr>
        <td><%= product.id %></td>
        <td><%= product.name %></td>
        <td><%= product.price %></td>
        <td><%= product.released_on %></td>
      </tr>
    <% end %>
  </tbody>
</table>

そして、最後にルートを追加します。

Rails.application.routes.draw do
  root 'products#index'
  resources 'products', only: :index
end

rails sでサーバーを起動して画面を確認しましょう。
f:id:nipe880324:20141115151914p:plain:w380




2. CSVのアップロード機能の実装/

では、アップロード機能を実装します。

CSVを処理できるようにするために、Ruby標準ライブラリのcsvを追加します。

# config/application.rb
require File.expand_path('../boot', __FILE__)

require 'rails/all'
require 'csv'

ビューにファイルをアップロードする入力フィールドを追加します。

# app/views/products/index.html.erb

<!-- ページの一番下に追加 -->
<%= form_tag import_products_path, multipart: true do %>
  <%= file_field_tag :file %>
  <%= submit_tag "インポート" %>
<% end %>

ルートを追加します。

# config/routes.rb

resources 'products', only: :index do
  collection { post :import }
end

コントローラーでファイルを受け取り、リダイレクトします。

# app/controllers/products_controller.rb

def import
  # fileはtmpに自動で一時保存される
  Product.import(params[:file])
  redirect_to root_url, notice: "商品を追加しました。"
end

CSVを読み込んで、DBに登録するインポート処理を実装します。

# app/models/product.rb

class Product < ActiveRecord::Base

  def self.import(file)
    CSV.foreach(file.path, headers: true) do |row|
      # IDが見つかれば、レコードを呼び出し、見つかれなければ、新しく作成
      product = find_by(id: row["id"]) || new
      # CSVからデータを取得し、設定する
      product.attributes = row.to_hash.slice(*updatable_attributes)
      # 保存する
      product.save!
    end
  end

  # 更新を許可するカラムを定義
  def self.updatable_attributes
    ["name", "price", "released_on"]
  end
end

サーバーを再起動して、ファイルをアップロードします。

アップロードするファイルの内容は次の通りです。

id,name,price,released_on
10,一眼デジタルカメラ,300,2014-11-13
11,Bar,100,2014-12-01

では、アップロードしてみます。
アップロードすると、ID10の商品が更新され、ID11の商品が追加されています。
f:id:nipe880324:20141115152012p:plain:w380




3. Excelのインポート機能の実装/

次は、Excelのインポート機能の実装です。

rooというExcel, CSV, OpenOffice, GoogleSpreadSheetを開くことができるGemを利用します。

# Gemfile
gem 'roo'

バンドルインストールします。

bundle install

ファイル読み込みの箇所を拡張子に合わせて、Rooを使って読み込むようにします。

# app/models/product.rb

class Product < ActiveRecord::Base

  def self.import(file)
    spreadsheet = open_spreadsheet(file)
    header = spreadsheet.row(1)

    (2..spreadsheet.last_row).each do |i|
      # {カラム名 => 値, ...} のハッシュを作成する
      row = Hash[[header, spreadsheet.row(i)].transpose]

      # IDが見つかれば、レコードを呼び出し、見つかれなければ、新しく作成
      product = find_by(id: row["id"]) || new
      # CSVからデータを取得し、設定する
      product.attributes = row.to_hash.slice(*updatable_attributes)
      # 保存する
      product.save!
    end
  end

  def self.open_spreadsheet(file)
    case File.extname(file.original_filename)
    when '.csv'  then Roo::Csv.new(file.path,    nil, :ignore)
    when '.xls'  then Roo::Excel.new(file.path,  nil, :ignore)
    when '.xlsx' then Roo::Excelx.new(file.path, nil, :ignore)
    when '.ods'  then Roo::OpenOffice.new(file.path, nil, :ignore)
    else raise "Unknown file type: #{file.original_filename}"
    end
  end

  ...
end

では、サーバーを再起動して、ファイルをアップロードしましょう。
今回は、OpenOfficeで次のようなファイルを作成しました。
f:id:nipe880324:20141115152141p:plain:w320

アップロードすると、ID11の商品が更新され、ID12の商品が追加されていることがわかります。
f:id:nipe880324:20141115152225p:plain:w380


以上です。