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

Rails Webook

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

RailsでCSV/Excelのダウンロード機能の実装方法

CSV/Excel

RailsでCSVやExcelをダウンロード(エクスポート)する方法を説明します。
Ransackなどで検索機能をつければ、検索結果に応じたCSV/Excel出力も可能です。

動作確認

  • Ruby 2.1
  • Rails 4.1

目次

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


Railsプロジェクトの作成

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

rails new csv_export_test
cd csv_export_test

そして、必要なコントローラー、ビュー、モデルを作成します。
製造者(Manufacture)と製品(Product)が1対Nの関係にします。

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

モデルにAssociationを追加します。

# app/models/manufacture.rb

class Manufacture < ActiveRecord::Base
  has_many :products
end


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

# db/seeds.rb

a = Manufacture.create! name: "Aエレクトリック"
%w(レコーダー イヤホン マイク Webカメラ).each do |name|
  random = [*1..5].sample  # 1-5からランダム値を取得
  a.products.create! name: name, price: random * 1000, released_on: random.day.ago
end

b = Manufacture.create! name: "B工業"
%w(洗濯機 冷蔵庫 エアコン).each do |name|
  random = [*1..5].sample  # 1-5からランダム値を取得
  b.products.create! name: name, price: random * 10000, released_on: random.day.ago
end

c = Manufacture.create! name: "C電器"
%w(ノートPC 40型TV デジタルカメラ).each do |name|
  random = [*1..5].sample  # 1-5からランダム値を取得
  c.products.create! name: name, price: random * 10000, released_on: random.day.ago
end

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

rake db:seed

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

# app/contollers/products_contoller.rb
class ProductsController < ApplicationController
  def index
    # N+1問題のため、allではなくincludes
    @products = Product.includes(:manufacture)
  end
end


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

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

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>名前</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>
        <td><%= product.manufacture.name %></td>
      </tr>
    <% end %>
  </tbody>
</table>

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

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

rails sでサーバーを起動して画面を確認しましょう。

f:id:nipe880324:20141115134717p:plain:w380




CSVのダウンロード機能の実装

では、この画面に表示している内容をCSVで出力するようにしてみましょう。

まず、Ruby標準付属のCSVクラスをRailsで使えるように requireを追加します。

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

require 'rails/all'
require 'csv'
...

次にCSVのダウンロードボタンを追加します。
format: "csv"をパスの引数に指定することで、
「"/products"」が「"/products.csv"」というURLになります。

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

<!-- 一番下に追加 -->
<p>
  Download:
  <%= link_to "CSV", products_path(format: "csv") %>
</p>

そして、コントローラーにrespond_toを追加し、
.csvでアクセスされた場合の処理を追記します。

# app/contollers/products_contoller.rb
class ProductsController < ApplicationController
  def index
    @products = Product.includes(:manufacture)

    respond_to do |format|
      format.html
      format.csv { send_data @products.to_csv }
    end
  end
end


そして、to_csvメソッドでCSVを作成する処理を実装します。

# app/models/product.rb

class Product < ActiveRecord::Base
  belongs_to :manufacture

  def self.to_csv
    CSV.generate do |csv|
      # column_namesはカラム名を配列で返す
      # 例: ["id", "name", "price", "released_on", ...]
      csv << column_names
      all.each do |product|
        # attributes はカラム名と値のハッシュを返す
        # 例: {"id"=>1, "name"=>"レコーダー", "price"=>3000, ... }
        # valudes_at はハッシュから引数で指定したキーに対応する値を取り出し、配列にして返す
        # 下の行は最終的に column_namesで指定したvalue値の配列を返す
        csv << product.attributes.values_at(*column_names)
      end
    end
  end

では、サーバーを再起動し、「CSV」ボタンを押して、CSVファイルをダウンロードしましょう。

中身は次のようになっています。
f:id:nipe880324:20141115134746p:plain:w320


to_csvメソッドでcolumn_namesを指定しているため、画面と同じ内容ではありません。
画面と同じになるように少し微調整をします。

# app/models/product.rb

class Product < ActiveRecord::Base
  belongs_to :manufacture

  def self.to_csv
    CSV.generate do |csv|
      csv << csv_column_names
      all.each do |product|
        csv << product.csv_column_values
      end
    end
  end

  def self.csv_column_names
    ["ID", "商品名", "値段", "発売日", "製造元"]
  end

  def csv_column_values
    [id, name, price, released_on, manufacture.name]
  end
end

では、ダウンロードファイルを見てみましょう。画面と同じになりました。
f:id:nipe880324:20141115134802p:plain:w320




Excelのダウンロード機能の実装

では、次は、Excelで出力するようにしてみます。
こちらもCSVとほぼ同じです。

Excelのダウンロードボタンを追加します。

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

<p>
  Download:
  <%= link_to "CSV", products_path(format: "csv") %>
  |
  <%= link_to "Excel", products_path(format: "xls") %>
</p>

そして、コントローラーにxlsを追加します。

# app/contollers/products_contoller.rb

class ProductsController < ApplicationController
  def index
    @products = Product.includes(:manufacture)

    respond_to do |format|
      format.html
      format.csv { send_data @products.to_csv }
      format.xls { send_data @products.to_csv(col_sep: "\t") }
    end
  end
end

そして、CSV.generateにオプションを指定するようにします。

# app/models/product.rb

  def self.to_csv(options = {})
    CSV.generate(options) do |csv|
      csv << csv_column_names
      all.each do |product|
        csv << product.csv_column_values
      end
    end
  end

そして、RailsからExcelを返せるようにするためにMIME TYPEを追加します。

# config/initializers/mime_types.rb

Mime::Type.register "application/xls", :xls

では、サーバーを再起動して、Excelファイルがダウンロードできることを確認しましょう。
f:id:nipe880324:20141115134824p:plain:w380